diff --git a/src/posts/0010-Haskell-Now/index.org b/src/posts/0010-Haskell-Now/index.org index 5e75661..70b61df 100644 --- a/src/posts/0010-Haskell-Now/index.org +++ b/src/posts/0010-Haskell-Now/index.org @@ -13,7 +13,7 @@ A very short and intense introduction to Haskell. This is an update of my old (2012) article. A lot of things have changed since then. -And I took the time to read it again. +I took the time to read it again. #+end_notes #+begin_quote @@ -162,7 +162,7 @@ The article contains five parts: - More on infinite tree; a more math oriented discussion about infinite trees -** Helpers :noexport: +** Helpers :noexport: :PROPERTIES: :CUSTOM_ID: helpers :END: @@ -3476,29 +3476,12 @@ You should understand the essence of the Haskell language. But now, to really be able to create something useful, you will need to also understand not only the language but the ecosystem around it. -There are many different people involved with Haskell and some with quite -different point of view. -There are: +This chapter will focus on how to build applications with Haskell. +How to use libraries inside your project. +And perhaps provide a few pointers to start to organize your projects. -- /core contributers/, that write papers, contribute to GHC and participate - in language update proposals, -- /Haskell focused tools creators/, - people creating tools useful for the Haskell community like IDE engines - and plugins, tools to help building Haskell projects, etc... -- /library creators/, -- /application creators/, users that use the resources from the Haskell - ecosystem to create useful applications or solve problems using it. - -Of course I am missing a lot of categories and also most Haskellers -participate in many different ones. -But it is important to bear in mind that Haskell is a free software for -anyone to use and participate in its enhancements. -In particular, about the missing categories are just Haskell hobyist that -write blog post about it, and participate in the community. - -This entire section will be about, writing real-world applications. -How to start a new project, how to search for the libraries and use the -tools around Haskell for example. +Note application development is easier to introduce than library development. +Mostly because dependency management will be a lot easier. #+begin_notes If you find those part too hard, do not feel discouraged though, most @@ -3511,57 +3494,593 @@ it really "clicked" for them. :CUSTOM_ID: start-a-new-project :END: -There are multiple way to start a new project. -The community exists for some time now, I can think of 3 "starter pack": +There are many different way to start. +The most common one is certainly to use =cabal-install=. +Another popular option is to use =stack=. +=stack= add a layer on top of =cabal-install= and use fixed set of libraries +known to compile together. +The last option is to use =cabal-install= but take care of the dependencies +of your project via =nix=. -1. Use =cabal-install= (the source) -2. Use =stack= -3. Use =nix= +The last option is often considered as the most complex and difficult for +beginner. +And guess what? +Against all odds, I will introduce haskell project development via =nix=. -All methods will in the end need =cabal-install=. -While, both the =cabal-install= and =stack= method are "easy" to start -with, the =nix= method is for more advanced user that want to invest time -in learning =nix=. +Still, you shall not be intimidated. Look: -Also, my personal feeling is that for beginners the =stack= method should -be preferred. -By default it will "freeze" your dependencies and will improve your ability -to have reproducible builds. +- To create a new project the steps will be: + 1. run =nix-shell= (to have =cabal= executable in your PATH) + 2. run =cabal install -i= and answer a few questions + 3. copy a few =.nix= files in your project directory + 4. run another =nix-shell= in your new directory this time to enter in the + local dev env of your new project. -I wouldn't want to start any war on the tooling. -Technical choices like those are often the occasion for bikeshedding[fn:8] -discussions and end up like many programming debates (space vs tabs, vim vs -emacs, linux vs macOs, c vs c++, etc...) +- To add a new library: + 1. Just add it in the =.cabal= file, and enter again in your =nix-shell=. -So even though I will assume that you will use the =stack= method to create -your project, I still want to show how you could only use =cabal-install=. +I will just walk you through all the steps in detail. +And mostly I will tell you not to take care about most warning messages. +For our end-goal, those are mostly noise. +I am aware of the level of complexity that it looks like at first. +But really most of the apparent complexity is due to poor naming convention +and not to any fundenmental core difficulty. -[fn:8] From [[https://en.wiktionary.org/wiki/bikeshedding][wikitionary]] the term was coined as a metaphor to illuminate - Parkinson’s Law of Triviality. - Parkinson observed that a committee whose job is to approve plans for a - nuclear power plant may spend the majority of its time on relatively - unimportant but easy-to-grasp issues, such as what materials to use - for the staff bikeshed, while neglecting the design of the power - plant itself, which is far more important but also far more - difficult to criticize constructively. - It was popularized in the Berkeley Software Distribution community by - Poul-Henning Kamp and has spread from there to the software - industry at large. - -*** =cabal-install= +*** Bootstrap a project template files :PROPERTIES: -:CUSTOM_ID: -cabal-install- +:CUSTOM_ID: bootstrap-a-project-template-files :END: +1. put the [[file:shell.nix][shell.nix]] file in some directory +2. start =nix-shell --pure= +3. in the nix shell create a new directory and then +4. =cabal init -i= +5. You should use the default value for most questions except: + 1. Should I generate a simple project with sensible defaults? [default: y] n + 2. the package should build "Library AND Executable" (choice 3) + 3. Cabal specification 2.4 (choice 4) + 4. Application directory choose =app= (choice 3) + 5. Library directory choose =lib= (choice 3) + 6. Add informative comments, choose yes. +Here is a full interaction: + +#+begin_src +~/dev/hsenv> nix-shell + +[hs:hsenv]> mkdir my-app + +[hs:hsenv]> cd my-app/ + +[hs:my-app]> cabal init -i +Warning: The package list for 'hackage.haskell.org' does not exist. Run 'cabal +update' to download it. +Should I generate a simple project with sensible defaults? [default: y] n +What does the package build: + 1) Executable + 2) Library + 3) Library and Executable +Your choice? 3 +What is the main module of the executable: + * 1) Main.hs (does not yet exist, but will be created) + 2) Main.lhs (does not yet exist, but will be created) + 3) Other (specify) +Your choice? [default: Main.hs (does not yet exist, but will be created)] +Please choose version of the Cabal specification to use: + * 1) 1.10 (legacy) + 2) 2.0 (+ support for Backpack, internal sub-libs, '^>=' operator) + 3) 2.2 (+ support for 'common', 'elif', redundant commas, SPDX) + 4) 2.4 (+ support for '**' globbing) +Your choice? [default: 1.10 (legacy)] 4 +Package name? [default: my-app] +Package version? [default: 0.1.0.0] +Please choose a license: + 1) GPL-2.0-only + 2) GPL-3.0-only + 3) LGPL-2.1-only + 4) LGPL-3.0-only + 5) AGPL-3.0-only + 6) BSD-2-Clause + * 7) BSD-3-Clause + 8) MIT + 9) ISC + 10) MPL-2.0 + 11) Apache-2.0 + 12) LicenseRef-PublicDomain + 13) NONE + 14) Other (specify) +Your choice? [default: BSD-3-Clause] +Author name? [default: Yann Esposito (Yogsototh)] +Maintainer email? [default: yann.esposito@gmail.com] +Project homepage URL? +Project synopsis? +Project category: + * 1) (none) + 2) Codec + 3) Concurrency + 4) Control + 5) Data + 6) Database + 7) Development + 8) Distribution + 9) Game + 10) Graphics + 11) Language + 12) Math + 13) Network + 14) Sound + 15) System + 16) Testing + 17) Text + 18) Web + 19) Other (specify) +Your choice? [default: (none)] +Application (Main.hs) directory: + * 1) (none) + 2) src-exe + 3) app + 4) Other (specify) +Your choice? [default: (none)] 3 +Library source directory: + * 1) (none) + 2) src + 3) lib + 4) src-lib + 5) Other (specify) +Your choice? [default: (none)] 2 +Should I generate a test suite for the library? [default: y] +Test directory: + * 1) test + 2) Other (specify) +Your choice? [default: test] +What base language is the package written in: + * 1) Haskell2010 + 2) Haskell98 + 3) Other (specify) +Your choice? [default: Haskell2010] +Add informative comments to each field in the cabal file (y/n)? [default: n] y + +Guessing dependencies... + +Generating LICENSE... +Generating Setup.hs... +Generating CHANGELOG.md... +Generating src/MyLib.hs... +Generating app/Main.hs... +Generating test/MyLibTest.hs... +Generating my-app.cabal... + +Warning: no synopsis given. You should edit the .cabal file and add one. +You may want to edit the .cabal file and add a Description field. + +[hs:my-app]> +#+end_src + +#+begin_notes +Please ignore the following warning: + +#+begin_example +Warning: The package list for 'hackage.haskell.org' does not exist. Run 'cabal +update' to download it. +#+end_example + +Nix should take care of handling Haskell libraries not =cabal-install=. +No need to run =cabal update=. +#+end_notes + +After this step you should end up with the following set of files: + +#+begin_example +[hs:my-app]> tree +. +├── CHANGELOG.md +├── LICENSE +├── Setup.hs +├── app +│   └── Main.hs +├── src +│   └── MyLib.hs +├── my-app.cabal +└── test + └── MyLibTest.hs + +3 directories, 7 files +#+end_example + +*** Create a few nix files +:PROPERTIES: +:CUSTOM_ID: create-a-few-nix-files +:END: + +#+begin_notes +The goal of this tutorial is not to make you learn =nix= because it is a +bit complex, but to explain you a bit, =nix= use a a /configuration language/ +and not just a /configuration format/. +So to configure your =nix= environment you endup writing a /nix expression/ +in this /nix language/. +And thus you can call the content of one nix-file in another one for +example, or use variables. +#+end_notes + +The first file to create is the one that will pin the versions of all your +packages and libraries: + +{{{lnk(my-app/nixpkgs.nix)}}} +#+begin_src nix :tangle my-app/nixpkgs.nix :mkdirp t +import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {} +#+end_src + +The second file is the =default.nix= file: + +{{{lnk(my-app/default.nix)}}} +#+begin_src nix :tangle my-app/default.nix :mkdirp t +{ nixpkgs ? import ./nixpkgs.nix +, compiler ? "default" +, doBenchmark ? false }: +let + inherit (nixpkgs) pkgs; + name = "my-app"; + haskellPackages = pkgs.haskellPackages; + variant = if doBenchmark + then pkgs.haskell.lib.doBenchmark + else pkgs.lib.id; + drv = haskellPackages.callCabal2nix name ./. {}; +in +{ + my_project = drv; + shell = haskellPackages.shellFor { + # generate hoogle doc + withHoogle = true; + packages = p: [drv]; + # packages dependencies (by default haskellPackages) + buildInputs = with haskellPackages; + [ hlint + ghcid + cabal-install + cabal2nix + hindent + # # if you want to add some system lib like ncurses + # # you could by writing it like: + # pkgs.ncurses + ]; + # nice prompt for the nix-shell + shellHook = '' + export PS1="\n[${name}:\033[1;32m\]\W\[\033[0m\]]> " + ''; + }; +} +#+end_src + +It uses the =nixpkgs.nix= file. +But also you can configure it to enable/disable benchmarks while building +your application. +I do not expect you to understand what is really going on here, but a short +explanation is this file take cares of: + +1. use the pinned version of nixpkgs and should provide a working set of + haskell libraries. +2. read you =.cabal= file and find the set of libraries you depends on so + =nix= will be able to download them. +3. download a few useful packages for Haskell development, in particular + =hlint=, =ghcid=, =cabal-install=, =cabal2nix= and =hindent=. + I will talk about those tools later. +4. take care of handling the =nix-shell= prompt so you should see the name + of your project. + +The only things you should manipulate for a new fresh project should be the +=name= and perhaps the =buildInputs= list to add a few more libraries that +could be either Haskell libraries or any library =nix= know about (for +example =ncurses=, in that case you should write it =pkgs.ncurses=). + +The two last file simply use the =default.nix= file: + +The =shell.nix= file: + +{{{lnk(my-app/shell.nix)}}} +#+begin_src nix :tangle my-app/shell.nix :mkdirp t +(import ./. {}).shell +#+end_src + +And =release.nix=: + +{{{lnk(my-app/release.nix)}}} +#+begin_src nix :tangle my-app/release.nix :mkdirp t +let + def = import ./. {}; +in + { my_project = def.my_project; } +#+end_src + +So download those files as well as this =.gitignore= file: + + +{{{lnk(my-app/.gitignore)}}} +#+begin_src gitignore :tangle my-app/.gitignore :mkdirp t +dist-newstyle/ +result +#+end_src + +*** Checking your environment +:PROPERTIES: +:CUSTOM_ID: checking-your-environment +:END: + +Now you should see those files in your project: + +#+begin_example +[hs:my-app]> tree +. +├── CHANGELOG.md +├── LICENSE +├── Setup.hs +├── app +│   └── Main.hs +├── default.nix +├── src +│   └── MyLib.hs +├── my-app.cabal +├── nixpkgs.nix +├── release.nix +├── shell.nix +└── test + └── MyLibTest.hs + +3 directories, 11 files +#+end_example + +You shall now enter =nix-shell= again, but in your =my-app= directory this time. + +#+begin_example +[hs:my-app]> nix-shell +warning: Nix search path entry '/nix/var/nix/profiles/per-user/root/channels' does not exist, ignoring +building '/nix/store/j3hi4wm9996wfga61arc2917klfgspwr-cabal2nix-my-app.drv'... +installing +warning: Nix search path entry '/nix/var/nix/profiles/per-user/root/channels/nixpkgs' does not exist, ignoring +warning: file 'nixpkgs' was not found in the Nix search path (add it using $NIX_PATH or -I), at (string):1:9; will use bash from your environment + +[my-app:my-app]> which ghcid +/nix/store/ckps9wgbmpckxdvs42p6sqz64dfqiv35-ghcid-0.7.5-bin/bin/ghcid + +[my-app:my-app]> cabal run my-app +Build profile: -w ghc-8.6.5 -O1 +In order, the following will be built (use -v for more details): + - my-app-0.1.0.0 (src) (first run) + - my-app-0.1.0.0 (exe:my-app) (first run) +Configuring library for my-app-0.1.0.0.. +Preprocessing library for my-app-0.1.0.0.. +Building library for my-app-0.1.0.0.. +[1 of 1] Compiling MyLib ( src/MyLib.hs, /Users/y/hsenv/my-app/dist-newstyle/build/x86_64-osx/ghc-8.6.5/my-app-0.1.0.0/build/MyLib.o ) +Configuring executable 'my-app' for my-app-0.1.0.0.. +Preprocessing executable 'my-app' for my-app-0.1.0.0.. +Building executable 'my-app' for my-app-0.1.0.0.. +[1 of 1] Compiling Main ( app/Main.hs, /Users/y/hsenv/my-app/dist-newstyle/build/x86_64-osx/ghc-8.6.5/my-app-0.1.0.0/x/my-app/build/my-app/my-app-tmp/Main.o ) +Linking /Users/y/hs-env/my-app/dist-newstyle/build/x86_64-osx/ghc-8.6.5/my-app-0.1.0.0/x/my-app/build/my-app/my-app ... +Hello, Haskell! +someFunc +#+end_example + +Great! It works! +Try to run it again: + +#+begin_example +[my-app:my-app]> cabal run my-app +Up to date +Hello, Haskell! +someFunc +#+end_example + +This time, the compilation is not done again. +=cabal= is smart enough not to repeat the compilation again. + +#+begin_notes +You could also use =nix-build= to compile your app. +I think this is nice to do for releases. +But for development, you should use =cabal=. +#+end_notes + +*** Add a library +:PROPERTIES: +:CUSTOM_ID: add-a-library +:END: + +#+begin_notes +{{{tldr}}} do not be afraid by the lenght of this section in fact, this is +straightforward. +I just take a lot of time to go through all intermediate steps. + +1. add the library in the =build-depends= inside your =.cabal= file. +2. restart =nix-shell= to download the new dependencies. +#+end_notes + +If you open the =my-app.cabal= file in an editor you should see a =library= +section and and =executable my-app= section. +In particular for each section you can see a =build-depends= sub-section as +this one: + +#+begin_src cabal +... +library + ... + build-depends: base ^>=4.12.0.0 + ... +executable my-app + ... + build-depends: base ^>=4.12.0.0, my-app + ... +#+end_src + +#+begin_notes +The =^>=4.12.0.0= means that it should use the latest non breaking version +of the haskell package =base=. The author of the =base= package are +responsible not to break the API for minor releases. +Haskell libs uses a 4 number versionning quite similar to the semantic +versionning scheme with just another minor number for non visible changes. +I will not argue much, but mainly, semantic versionning and Haskell +versionning are just a "right to break things to your users". + +I don't want to talk a lot more about this, but, it would be nice if more +people would watch this talk[fn:9] related to versionning. + +[fn:9]: [[https://www.youtube.com/watch?v=oyLBGkS5ICk][Spec-ulation Keynote - Rich Hickey]] + +If you want to know more about Haskell versionning convention: +https://pvp.haskell.org +#+end_notes + +Add the =protolude= lib in the library build-depends like this: + +#+begin_src cabal +... +library + ... + build-depends: base ^>=4.12.0.0, + protolude + ... +executable my-app + ... + build-depends: base ^>=4.12.0.0, my-app + ... +#+end_src + +#+begin_notes +I did not included a version constraint here. +This is ok if you do not deploy your library publicly. +This would be absolutely awful if you deploy your library publicly. +So while developing a private app nobody can see except you, nothing is +wrong with this. +But I would encourage you to write those version bounds. +It is sane to do that, but be warned that your lib might rot if you want it +to be part of a working set of libs. +So you might be pinged time to time to update some bounds or to adap your +code to the breaking change of a lib you are using. +Do not think too much about this. +This is generally quite trivial work to do to maintain your lib into a +working lib set. +#+end_notes + +Now that you have added =protolude= modify slightly the code of your app to +use it. +Change the code inside =src/MyLib.hs=: + +{{{lnk(my-app/src/MyLib.hs)}}} +#+begin_src haskell :tangle my-app/src/MyLib.hs :mkdirp t +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} +module MyLib (someFunc) where + +import Protolude + +someFunc :: IO () +someFunc = putText "someFunc" +#+end_src + +Please do not try to search right now about what this change is doing. +It should work mostly as before. +The goal here is just to check that you can use another library easily. + +So now you should get out of the =nix-shell= because =nix= dependencies +changed. +Generally just type =^D= (=Ctrl-d=) then launch =nix-shell --pure=. + +#+begin_example +[my-app:my-app]> cabal build +Warning: The package list for 'hackage.haskell.org' does not exist. Run 'cabal +update' to download it. +Resolving dependencies... +cabal: Could not resolve dependencies: +[__0] trying: my-app-0.1.0.0 (user goal) +[__1] unknown package: protolude (dependency of my-app) +[__1] fail (backjumping, conflict set: my-app, protolude) +After searching the rest of the dependency tree exhaustively, these were the +goals I've had most trouble fulfilling: my-app, protolude + + +[my-app:my-app]> exit + +[hs:my-app]> nix-shell +warning: Nix search path entry '/nix/var/nix/profiles/per-user/root/channels' does not exist, ignoring +building '/nix/store/sr4838rnmzn30j3qc5ray4i2n6n0p8pq-cabal2nix-my-app.drv'... +installing + +[my-app:my-app]> cabal build +Build profile: -w ghc-8.6.5 -O1 +In order, the following will be built (use -v for more details): + - my-app-0.1.0.0 (lib) (file src/MyLib.hs changed) + - my-app-0.1.0.0 (exe:my-app) (configuration changed) +Preprocessing library for my-app-0.1.0.0.. +Building library for my-app-0.1.0.0.. +[1 of 1] Compiling MyLib ( src/MyLib.hs, .../my-app/dist-newstyle/build/x86_64-osx/ghc-8.6.5/my-app-0.1.0.0/build/MyLib.o ) +Configuring executable 'my-app' for my-app-0.1.0.0.. +Preprocessing executable 'my-app' for my-app-0.1.0.0.. +Building executable 'my-app' for my-app-0.1.0.0.. +[1 of 1] Compiling Main ( app/Main.hs, .../my-app/dist-newstyle/build/x86_64-osx/ghc-8.6.5/my-app-0.1.0.0/x/my-app/build/my-app/my-app-tmp/Main.o ) [MyLib changed] +Linking .../my-app/dist-newstyle/build/x86_64-osx/ghc-8.6.5/my-app-0.1.0.0/x/my-app/build/my-app/my-app ... + +[my-app:my-app]> cabal run my-app +Up to date +Hello, Haskell! +someFunc +#+end_example + +Yes! + +*** Better defaults +:PROPERTIES: +:CUSTOM_ID: better-defaults +:END: + +Some of the default values in the cabal file are not the best for a +professional and serious application development unfortunately. +First, let create a new block called =common professional-properties= +that will help us not repeat ourselve much and show more warning during compilation. + +#+begin_src cabal +common professional-properties + default-language: Haskell2010 + build-depends: + base ^>=4.12.0.0 + ghc-options: + -Wall + -Wcompat + -Wincomplete-uni-patterns + -Wredundant-constraints + -Wnoncanonical-monad-instances + -- -Werror + -- -O2 +#+end_src + +This should then be used with import in all other sections (=library=, +=executable= and =test=). +Also add the =ghc-options= to enable the use of all core by default. +This might not always be a good idea. +But I think this is generally a better default for most modern application. + +#+begin_src cabal +library + import: professional-properties + build-depends: protolude + ... + +executable my-app + import: professional-properties + ghc-options: + -- enable parallelism + -threaded + "-with-rtsopts=-N" + ... + +test-suite my-app-test + import: professional-properties + ... +#+end_src + +You can download the final cabal file: [[file:my-app/my-app.cabal][my-app.cabal]] ** Command line application :PROPERTIES: :CUSTOM_ID: command-line-application :END: - - ** Web Application :PROPERTIES: :CUSTOM_ID: web-application diff --git a/src/posts/0010-Haskell-Now/infinite_tree.hs b/src/posts/0010-Haskell-Now/infinite_tree.hs index aa42d00..3798917 100644 --- a/src/posts/0010-Haskell-Now/infinite_tree.hs +++ b/src/posts/0010-Haskell-Now/infinite_tree.hs @@ -1,21 +1,21 @@ -import Data.Tree (Tree,Forest(..)) -import qualified Data.Tree as Tree + import Data.Tree (Tree,Forest(..)) + import qualified Data.Tree as Tree -data BinTree a = Empty - | Node a (BinTree a) (BinTree a) - deriving (Eq,Ord,Show) + data BinTree a = Empty + | Node a (BinTree a) (BinTree a) + deriving (Eq,Ord,Show) --- | Function to transform our internal BinTree type to the --- type of Tree declared in Data.Tree (from containers package) --- so that the function Tree.drawForest can use -binTreeToForestString :: (Show a) => BinTree a -> Forest String -binTreeToForestString Empty = [] -binTreeToForestString (Node x left right) = - [Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))] + -- | Function to transform our internal BinTree type to the + -- type of Tree declared in Data.Tree (from containers package) + -- so that the function Tree.drawForest can use + binTreeToForestString :: (Show a) => BinTree a -> Forest String + binTreeToForestString Empty = [] + binTreeToForestString (Node x left right) = + [Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))] --- | Function that given a BinTree print a representation of it in the console -prettyPrintTree :: (Show a) => BinTree a -> IO () -prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString + -- | Function that given a BinTree print a representation of it in the console + prettyPrintTree :: (Show a) => BinTree a -> IO () + prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString nullTree = Node 0 nullTree nullTree diff --git a/src/posts/0010-Haskell-Now/infinite_tree_2.hs b/src/posts/0010-Haskell-Now/infinite_tree_2.hs index 30aefc3..ba6a8a6 100644 --- a/src/posts/0010-Haskell-Now/infinite_tree_2.hs +++ b/src/posts/0010-Haskell-Now/infinite_tree_2.hs @@ -1,30 +1,30 @@ -import Data.Tree (Tree,Forest(..)) -import qualified Data.Tree as Tree + import Data.Tree (Tree,Forest(..)) + import qualified Data.Tree as Tree -data BinTree a = Empty - | Node a (BinTree a) (BinTree a) - deriving (Eq,Ord,Show) + data BinTree a = Empty + | Node a (BinTree a) (BinTree a) + deriving (Eq,Ord,Show) --- | Function to transform our internal BinTree type to the --- type of Tree declared in Data.Tree (from containers package) --- so that the function Tree.drawForest can use -binTreeToForestString :: (Show a) => BinTree a -> Forest String -binTreeToForestString Empty = [] -binTreeToForestString (Node x left right) = - [Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))] + -- | Function to transform our internal BinTree type to the + -- type of Tree declared in Data.Tree (from containers package) + -- so that the function Tree.drawForest can use + binTreeToForestString :: (Show a) => BinTree a -> Forest String + binTreeToForestString Empty = [] + binTreeToForestString (Node x left right) = + [Tree.Node (show x) ((binTreeToForestString left) ++ (binTreeToForestString right))] --- | Function that given a BinTree print a representation of it in the console -prettyPrintTree :: (Show a) => BinTree a -> IO () -prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString + -- | Function that given a BinTree print a representation of it in the console + prettyPrintTree :: (Show a) => BinTree a -> IO () + prettyPrintTree = putStrLn . Tree.drawForest . binTreeToForestString --- | take all element of a BinTree up to some depth -treeTakeDepth _ Empty = Empty -treeTakeDepth 0 _ = Empty -treeTakeDepth n (Node x left right) = let - nl = treeTakeDepth (n-1) left - nr = treeTakeDepth (n-1) right - in - Node x nl nr + -- | take all element of a BinTree up to some depth + treeTakeDepth _ Empty = Empty + treeTakeDepth 0 _ = Empty + treeTakeDepth n (Node x left right) = let + nl = treeTakeDepth (n-1) left + nr = treeTakeDepth (n-1) right + in + Node x nl nr iTree = Node 0 (dec iTree) (inc iTree) where diff --git a/src/posts/0010-Haskell-Now/my-app/.gitignore b/src/posts/0010-Haskell-Now/my-app/.gitignore new file mode 100644 index 0000000..6d7e2df --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/.gitignore @@ -0,0 +1,2 @@ +dist-newstyle/ +result diff --git a/src/posts/0010-Haskell-Now/my-app/CHANGELOG.md b/src/posts/0010-Haskell-Now/my-app/CHANGELOG.md new file mode 100644 index 0000000..7674ab0 --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for my-app + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/src/posts/0010-Haskell-Now/my-app/LICENSE b/src/posts/0010-Haskell-Now/my-app/LICENSE new file mode 100644 index 0000000..1343de7 --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2019, Yann Esposito (Yogsototh) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Yann Esposito (Yogsototh) nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/posts/0010-Haskell-Now/my-app/Setup.hs b/src/posts/0010-Haskell-Now/my-app/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/src/posts/0010-Haskell-Now/my-app/app/Main.hs b/src/posts/0010-Haskell-Now/my-app/app/Main.hs new file mode 100644 index 0000000..60d904e --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/app/Main.hs @@ -0,0 +1,8 @@ +module Main where + +import qualified MyLib (someFunc) + +main :: IO () +main = do + putStrLn "Hello, Haskell!" + MyLib.someFunc diff --git a/src/posts/0010-Haskell-Now/my-app/cabal.project.local b/src/posts/0010-Haskell-Now/my-app/cabal.project.local new file mode 100644 index 0000000..e69de29 diff --git a/src/posts/0010-Haskell-Now/my-app/default.nix b/src/posts/0010-Haskell-Now/my-app/default.nix new file mode 100644 index 0000000..f0d6574 --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/default.nix @@ -0,0 +1,35 @@ +{ nixpkgs ? import ./nixpkgs.nix +, compiler ? "default" +, doBenchmark ? false }: +let + inherit (nixpkgs) pkgs; + name = "my-app"; + haskellPackages = pkgs.haskellPackages; + variant = if doBenchmark + then pkgs.haskell.lib.doBenchmark + else pkgs.lib.id; + drv = haskellPackages.callCabal2nix name ./. {}; +in +{ + my_project = drv; + shell = haskellPackages.shellFor { + # generate hoogle doc + withHoogle = true; + packages = p: [drv]; + # packages dependencies (by default haskellPackages) + buildInputs = with haskellPackages; + [ hlint + ghcid + cabal-install + cabal2nix + hindent + # # if you want to add some system lib like ncurses + # # you could by writing it like: + # pkgs.ncurses + ]; + # nice prompt for the nix-shell + shellHook = '' + export PS1="\n[${name}:\033[1;32m\]\W\[\033[0m\]]> " + ''; + }; +} diff --git a/src/posts/0010-Haskell-Now/my-app/my-app.cabal b/src/posts/0010-Haskell-Now/my-app/my-app.cabal new file mode 100644 index 0000000..126044a --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/my-app.cabal @@ -0,0 +1,97 @@ +cabal-version: 2.4 + +-- Initial package description 'my-app.cabal' generated by 'cabal init'. +-- For further documentation, see http://haskell.org/cabal/users-guide/ + +-- The name of the package. +name: my-app + +-- The package version. See the Haskell package versioning policy (PVP) +-- for standards guiding when and how versions should be incremented. +-- https://pvp.haskell.org +-- PVP summary: +-+------- breaking API changes +-- | | +----- non-breaking API additions +-- | | | +--- code changes with no API change +version: 0.1.0.0 + +-- A short (one-line) description of the package. +synopsis: app used for demonstration + +-- A longer description of the package. +-- description: + +-- A URL where users can report bugs. +-- bug-reports: + +-- The license under which the package is released. +license: BSD-3-Clause + +-- The file containing the license text. +license-file: LICENSE + +-- The package author(s). +author: Yann Esposito (Yogsototh) + +-- An email address to which users can send suggestions, bug reports, and +-- patches. +maintainer: yann.esposito@gmail.com + +-- A copyright notice. +-- copyright: + +-- category: + +-- Extra files to be distributed with the package, such as examples or a +-- README. +extra-source-files: CHANGELOG.md + +common shared-properties + default-language: Haskell2010 + build-depends: + base ^>=4.12.0.0 + ghc-options: + -Wall + -Wcompat + -Wincomplete-uni-patterns + -Wredundant-constraints + -Wnoncanonical-monad-instances + -- -Werror + -- -O2 + +library + import: shared-properties + -- Modules exported by the library. + exposed-modules: MyLib + build-depends: protolude + -- Modules included in this library but not exported. + -- other-modules: + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + -- Directories containing source files. + hs-source-dirs: src + +executable my-app + import: shared-properties + -- .hs or .lhs file containing the Main module. + main-is: Main.hs + build-depends: my-app + ghc-options: + -- enable parallelism + -threaded + "-with-rtsopts=-N" + -- Modules included in this executable, other than Main. + -- other-modules: + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + -- Directories containing source files. + hs-source-dirs: app + +test-suite my-app-test + import: shared-properties + -- The interface type and version of the test suite. + type: exitcode-stdio-1.0 + -- The directory where the test specifications are found. + hs-source-dirs: test + -- The entrypoint to the test suite. + main-is: MyLibTest.hs + diff --git a/src/posts/0010-Haskell-Now/my-app/nixpkgs.nix b/src/posts/0010-Haskell-Now/my-app/nixpkgs.nix new file mode 100644 index 0000000..aae2b1d --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/nixpkgs.nix @@ -0,0 +1 @@ +import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {} diff --git a/src/posts/0010-Haskell-Now/my-app/release.nix b/src/posts/0010-Haskell-Now/my-app/release.nix new file mode 100644 index 0000000..8a1ee52 --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/release.nix @@ -0,0 +1,4 @@ +let + def = import ./. {}; +in + { my_project = def.my_project; } diff --git a/src/posts/0010-Haskell-Now/my-app/shell.nix b/src/posts/0010-Haskell-Now/my-app/shell.nix new file mode 100644 index 0000000..0d9af5e --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/shell.nix @@ -0,0 +1 @@ +(import ./. {}).shell diff --git a/src/posts/0010-Haskell-Now/my-app/src/MyLib.hs b/src/posts/0010-Haskell-Now/my-app/src/MyLib.hs new file mode 100644 index 0000000..8cf79c3 --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/src/MyLib.hs @@ -0,0 +1,8 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} +module MyLib (someFunc) where + +import Protolude + +someFunc :: IO () +someFunc = putText "someFunc" diff --git a/src/posts/0010-Haskell-Now/my-app/test/MyLibTest.hs b/src/posts/0010-Haskell-Now/my-app/test/MyLibTest.hs new file mode 100644 index 0000000..3e2059e --- /dev/null +++ b/src/posts/0010-Haskell-Now/my-app/test/MyLibTest.hs @@ -0,0 +1,4 @@ +module Main (main) where + +main :: IO () +main = putStrLn "Test suite not yet implemented." diff --git a/src/posts/0010-Haskell-Now/shell.nix b/src/posts/0010-Haskell-Now/shell.nix index e0d8708..9918136 100644 --- a/src/posts/0010-Haskell-Now/shell.nix +++ b/src/posts/0010-Haskell-Now/shell.nix @@ -1,26 +1,26 @@ -{ nixpkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {} }: -let - inherit (nixpkgs) pkgs; - inherit (pkgs) haskellPackages; + { nixpkgs ? import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {} }: + let + inherit (nixpkgs) pkgs; + inherit (pkgs) haskellPackages; - haskellDeps = ps: with ps; [ - base - protolude - containers - ]; + haskellDeps = ps: with ps; [ + base + protolude + containers + ]; - ghc = haskellPackages.ghcWithPackages haskellDeps; + ghc = haskellPackages.ghcWithPackages haskellDeps; - nixPackages = [ - ghc - pkgs.gdb - haskellPackages.cabal-install - ]; -in -pkgs.stdenv.mkDerivation { - name = "env"; - buildInputs = nixPackages; - shellHook = '' - export PS1="\n[hs:\033[1;32m\]\W\[\033[0m\]]> " - ''; -} + nixPackages = [ + ghc + pkgs.gdb + haskellPackages.cabal-install + ]; + in + pkgs.stdenv.mkDerivation { + name = "env"; + buildInputs = nixPackages; + shellHook = '' + export PS1="\n[hs:\033[1;32m\]\W\[\033[0m\]]> " + ''; + }