Haskell: Difference between revisions

Pigs (talk | contribs)
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 ==
There are multiples ways to develop in Haskell on Nix depending on the simplicity of the project and on whether one want to benefit from the reproducibility offered by nix or not. Below is an image to help you in your choice:
[[File:haskell_choice.png]]


{{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 simple scripts, you can directly use nix-shell to get a redistributable Haskell script that you can run on any Nix system with <code>./my-script.hs</code>:
For redistributable Haskell scripts on any Nix system, you can use a nix-shell shebang.
<syntaxhighlight lang=bash>
<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 "haskellPackages.ghcWithPackages (pkgs: [ pkgs.turtle ])"
{-# LANGUAGE OverloadedStrings #-}
import Turtle


main = do
main = do
   -- do stuff
   -- do stuff
   putStrLn "Hello world from a distributable Haskell script!"
   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 "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ cabal-install ])"
$  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 "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ stack ])"
$ 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 "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ cabal-install ])" --run "cabal init"
$ 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) ===


<code>shellFor</code> is similar to <code>developPackage</code> but (slightly) more complicated to also allow you to develop multiples packages at the same time (similar to <code>cabal.project</code>). Note that contrary to <code>developPackage</code> I don't think that <code>shellFor</code> can output a derivation.
[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:


<syntaxhighlight lang=text>
{{file|cabal.project|nix|
<nowiki>
packages:
packages:
   frontend/
   frontend/
   backend/
   backend/
</syntaxhighlight>
</nowiki>
}}


Finally create a file <code>shell.nix</code> containing:
Finally you define a nix shell in <code>shell.nix</code> containing:


<syntaxhighlight lang="nix">
{{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 ];
   }
   }
</syntaxhighlight>
</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 new-build all
$ 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>:
<syntaxhighlight lang="nix">
 
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
</syntaxhighlight>
</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 ==