Haskell: Difference between revisions
Refactor some formatting, fill out more details on using haskell flake-parts |
This flow chart is wrong or at least so much oversimplified that it is actively misleading |
||
| (5 intermediate revisions by 2 users not shown) | |||
| Line 4: | Line 4: | ||
== How to develop with Haskell and Nix == | == How to develop with Haskell and Nix == | ||
{{Note|{{nixos:package|haskellPackages}} is a synonym of <code>haskell.packages.ghcXYZ</code> where <code>XYZ</code> is the current default version of GHC in nixpkgs. However you can use a different version by replacing <code>haskellPackages</code> with the wanted package, for instance use <code>haskell.compiler.ghc884</code> to use GHC 8.8.4. You can get the full list of available GHC versions using: | {{Note|{{nixos:package|haskellPackages}} is a synonym of <code>haskell.packages.ghcXYZ</code> where <code>XYZ</code> is the current default version of GHC in nixpkgs. However you can use a different version by replacing <code>haskellPackages</code> with the wanted package, for instance use <code>haskell.compiler.ghc884</code> to use GHC 8.8.4. You can get the full list of available GHC versions using: | ||
| Line 25: | Line 21: | ||
=== Scripting === | === Scripting === | ||
For | For redistributable Haskell scripts on any Nix system, you can use a nix-shell shebang. | ||
<syntaxhighlight lang= | <syntaxhighlight lang="haskell">#!/usr/bin/env nix-shell | ||
#!/usr/bin/env nix-shell | #! nix-shell --pure -i runghc -p "ghc.withPackages (pkgs: [ pkgs.turtle ])" | ||
#!nix-shell --pure -i runghc -p " | {-# LANGUAGE OverloadedStrings #-} | ||
import Turtle | |||
main = do | main = do | ||
-- do stuff | -- do stuff | ||
echo "Hello world from a distributable Haskell script!"</syntaxhighlight> | |||
</syntaxhighlight> | |||
To remove the additional latency overhead of a nix-shell, add GHC to <code>environment.systemPackages</code> and call <code>runghc</code> in the shebang.<syntaxhighlight lang="nix"> | |||
environment.systemPackages = with pkgs; [ | |||
... | |||
(ghc.withPackages (hsPkgs: with hsPkgs; [ | |||
turtle # Faster startup time with all external shell commands | |||
shh # Piping operators and other goodies | |||
shh-extras # Try shh as an interactive shell | |||
... # ...anything else you want! | |||
]) | |||
]; | |||
</syntaxhighlight>Here's a basic example using the Shh module rather than Turtle, so it can use the pipe operator:<syntaxhighlight lang="haskell"> | |||
#!/usr/bin/env runghc | |||
{-# LANGUAGE TemplateHaskell #-} | |||
{-# LANGUAGE ExtendedDefaultRules #-} | |||
{-# LANGUAGE OverloadedStrings #-} | |||
import Shh | |||
To write inline Haskell scripts in nix-code, refer to [[Nix-writers#Haskell]]. | -- $(loadEnv SearchPath) -- Loads entire PATH, may be slow | ||
load SearchPath [ "dd", "sha512sum" ] | |||
main = do | |||
dd "if=/dev/urandom" "bs=4K" "count=1" |> sha512sum | |||
</syntaxhighlight>To write inline Haskell scripts in nix-code, refer to [[Nix-writers#Haskell]]. | |||
Read [[#Overrides]] if some packages are broken. | Read [[#Overrides]] if some packages are broken. | ||
| Line 43: | Line 61: | ||
[https://www.haskell.org/cabal/ Cabal] is the basic Haskell tool used to configure builds and is internally used by all the Haskell's packaging methods (including stack and nix). If one does not care about the reproducibility/caching offered by nix, it is always possible to use cabal like in a normal system: | [https://www.haskell.org/cabal/ Cabal] is the basic Haskell tool used to configure builds and is internally used by all the Haskell's packaging methods (including stack and nix). If one does not care about the reproducibility/caching offered by nix, it is always possible to use cabal like in a normal system: | ||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
$ nix-shell -p " | $ nix-shell -p "ghc.withPackages (pkgs: [ pkgs.cabal-install ])" | ||
$ cabal init | $ cabal init | ||
… | … | ||
| Line 58: | Line 76: | ||
Similarly you can use [https://docs.haskellstack.org/en/stable/ Stack] that let you find the appropriate version of the libraries for you if you do not want the caching offered by nix (stack will build all the dependencies): | Similarly you can use [https://docs.haskellstack.org/en/stable/ Stack] that let you find the appropriate version of the libraries for you if you do not want the caching offered by nix (stack will build all the dependencies): | ||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
$ nix-shell -p " | $ nix-shell -p "ghc.withPackages (pkgs: [ pkgs.stack ])" | ||
$ stack new my-project | $ stack new my-project | ||
$ cd my-project | $ cd my-project | ||
| Line 84: | Line 102: | ||
You can also use nix in place of stack to keep track of the dependencies in a reproducible way (note that while stack uses a solver to find a working set of dependencies, nix uses a fixed set of packages). Additionally you can benefit from the caching system offered by Nix. To that end, first create a cabal repository (nix also uses cabal internally): | You can also use nix in place of stack to keep track of the dependencies in a reproducible way (note that while stack uses a solver to find a working set of dependencies, nix uses a fixed set of packages). Additionally you can benefit from the caching system offered by Nix. To that end, first create a cabal repository (nix also uses cabal internally): | ||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
$ nix-shell -p " | $ nix-shell -p "ghc.withPackages (pkgs: [ pkgs.cabal-install ])" --run "cabal init" | ||
… | … | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 147: | Line 165: | ||
=== Using shellFor (multiple packages) === | === Using shellFor (multiple packages) === | ||
[https://nixos.org/manual/nixpkgs/stable/#haskell-shellFor shellFor] is similar to <code>developPackage</code> but (slightly) more complicated to also allow you to develop multiples packages at the same time (it can work in conjuction with [https://cabal.readthedocs.io/en/stable/cabal-project-description-file.html cabal.project]). Note that contrary to <code>developPackage</code> I don't think that <code>shellFor</code> can output a derivation. | |||
The idea is to first extend/override the set of haskell packages in order to add your projects as additional haskell packages (for instance using <code>haskellPackages.extend</code> and <code>packageSourceOverrides</code> that just need the path of the project to compile it), and then to use <code>haskellPackages.shellFor {packages= p: [p.myproject1 p.myproject2]}</code> to create a shell with all wanted packages. | The idea is to first extend/override the set of haskell packages in order to add your projects as additional haskell packages (for instance using <code>haskellPackages.extend</code> and <code>packageSourceOverrides</code> that just need the path of the project to compile it), and then to use <code>haskellPackages.shellFor {packages= p: [p.myproject1 p.myproject2]}</code> to create a shell with all wanted packages. | ||
| Line 153: | Line 171: | ||
For instance you can define your various projects in subfolders <code>./frontend</code> and <code>./backend</code> (you can use cabal init to create the content in each folder), then create a file <code>cabal.project</code> containing: | For instance you can define your various projects in subfolders <code>./frontend</code> and <code>./backend</code> (you can use cabal init to create the content in each folder), then create a file <code>cabal.project</code> containing: | ||
< | {{file|cabal.project|nix| | ||
<nowiki> | |||
packages: | packages: | ||
frontend/ | frontend/ | ||
backend/ | backend/ | ||
</ | </nowiki> | ||
}} | |||
Finally | Finally you define a nix shell in <code>shell.nix</code> containing: | ||
< | {{file|shell.nix|nix| | ||
with import <nixpkgs> {}; | <nowiki> | ||
with import </nowiki><<nowiki>nixpkgs</nowiki>><nowiki> {}; | |||
# We add our packages to the haskell package set | # We add our packages to the haskell package set | ||
(haskellPackages.extend (haskell.lib.compose.packageSourceOverrides { | (haskellPackages.extend (haskell.lib.compose.packageSourceOverrides { | ||
| Line 174: | Line 195: | ||
buildInputs = [ pkgs.python pkgs.cabal-install ]; | buildInputs = [ pkgs.python pkgs.cabal-install ]; | ||
} | } | ||
</ | </nowiki> | ||
}} | |||
then you can use cabal to develop incrementally your projects using for instance: | then you can use cabal to develop incrementally your projects using for instance: | ||
| Line 180: | Line 202: | ||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
$ nix-shell | $ nix-shell | ||
$ cabal | $ cabal build all | ||
</syntaxhighlight> | </syntaxhighlight> | ||
If you want to be able to compile a project non-incrementally with <code>nix-build</code> (say the backend in the above example) you can put in <code>default.nix</code>: | If you want to be able to compile a project non-incrementally with <code>nix-build</code> (say the backend in the above example) you can put in <code>default.nix</code>: | ||
< | |||
with import <nixpkgs> {}; | {{file|default.nix|nix| | ||
<nowiki> | |||
with import </nowiki><<nowiki>nixpkgs</nowiki>><nowiki> {}; | |||
# We add our packages to the haskell package set | # We add our packages to the haskell package set | ||
(haskellPackages.extend (haskell.lib.compose.packageSourceOverrides { | (haskellPackages.extend (haskell.lib.compose.packageSourceOverrides { | ||
| Line 191: | Line 215: | ||
backend = ./backend; | backend = ./backend; | ||
})).backend | })).backend | ||
</ | </nowiki> | ||
}} | |||
or if you want to create a single derivation file, you can use <code>if pkgs.lib.inNixShell then … else …</code> to output the shell when we start a shell and the packages when we want to build them. You can find [https://github.com/kowainik/summoner/blob/60de4f2f087e5bd2beaad9253e7eded731cfbaaf/default.nix here] an example. | or if you want to create a single derivation file, you can use <code>if pkgs.lib.inNixShell then … else …</code> to output the shell when we start a shell and the packages when we want to build them. You can find [https://github.com/kowainik/summoner/blob/60de4f2f087e5bd2beaad9253e7eded731cfbaaf/default.nix here] an example. | ||
| Line 383: | Line 409: | ||
=== IFD and Haskell === | === IFD and Haskell === | ||
<code>callCabal2nix</code>, which is implicitly used for building Haskell projects, uses IFD.[https://github.com/NixOS/templates/issues/28][https://discourse.nixos.org/t/another-simple-flake-for-haskell-development/18164/6]. This means that since IFD is disabled by default in certain nix commands,[https://github.com/NixOS/nix/pull/5253] the following commands will be broken for Haskell projects whose flake output specifies multiple system attributes: | <code>callCabal2nix</code>, which is implicitly used for building Haskell projects, uses IFD. Refer to this [https://github.com/NixOS/templates/issues/28 github issue] and [https://discourse.nixos.org/t/another-simple-flake-for-haskell-development/18164/6 discourse thread] for additional context. This means that since IFD is disabled by default in certain nix commands,[https://github.com/NixOS/nix/pull/5253] the following commands will be broken for Haskell projects whose flake output specifies multiple system attributes: | ||
* <code>nix flake show</code> | * <code>nix flake show</code> | ||
* <code>nix flake check</code> | * <code>nix flake check</code> | ||
=== GHCup === | |||
[https://www.haskell.org/ghcup/ GHCup] does not work properly on NixOS out of the box. NixOS cannot run dynamically linked executables built for generic Linux environments due to its runtime linker setup. For details and a workaround, see [https://nix.dev/guides/faq#how-to-run-non-nix-executables nix.dev's explanation of stub-ld]. | |||
In most cases there is little reason to use GHCup when working within a Nix-based system, as Nixpkgs can achieve the same goals such as managing multiple GHC versions and other Haskell tooling. | |||
== FAQ and resources == | == FAQ and resources == | ||