Nix introduction

This commit is contained in:
Yann Esposito (Yogsototh) 2019-12-31 01:00:04 +01:00
parent 0d1e4ff2a3
commit e24de08c34
Signed by untrusted user who does not match committer: yogsototh
GPG key ID: 7B19A4C650D59646
17 changed files with 836 additions and 120 deletions

View file

@ -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
Parkinsons 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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,2 @@
dist-newstyle/
result

View file

@ -0,0 +1,5 @@
# Revision history for my-app
## 0.1.0.0 -- YYYY-mm-dd
* First version. Released on an unsuspecting world.

View file

@ -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.

View file

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View file

@ -0,0 +1,8 @@
module Main where
import qualified MyLib (someFunc)
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
MyLib.someFunc

View file

@ -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\]]> "
'';
};
}

View file

@ -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

View file

@ -0,0 +1 @@
import (fetchTarball https://github.com/NixOS/nixpkgs/archive/19.09.tar.gz) {}

View file

@ -0,0 +1,4 @@
let
def = import ./. {};
in
{ my_project = def.my_project; }

View file

@ -0,0 +1 @@
(import ./. {}).shell

View file

@ -0,0 +1,8 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module MyLib (someFunc) where
import Protolude
someFunc :: IO ()
someFunc = putText "someFunc"

View file

@ -0,0 +1,4 @@
module Main (main) where
main :: IO ()
main = putStrLn "Test suite not yet implemented."

View file

@ -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\]]> "
'';
}