Haskell: Difference between revisions
→Using haskell-flake (flake-parts): Update links and include instructions on getting started |
This flow chart is wrong or at least so much oversimplified that it is actively misleading |
||
(7 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
[https://www.haskell.org/ Haskell] is a functional programming language. | [https://www.haskell.org/ Haskell] is a statically-typed, purely functional programming language with strong support for type inference and lazy evaluation. | ||
For detailed information on Haskell support in Nixpkgs, refer to the official {{Nixpkgs Manual|name=Nixpkgs Manual: Chapter - Haskell|anchor=#haskell}}. | |||
== 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: | |||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
Line 33: | Line 17: | ||
… | … | ||
</syntaxhighlight> | </syntaxhighlight> | ||
}} | |||
=== 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 | |||
-- do stuff | |||
echo "Hello world from a distributable Haskell script!"</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 | |||
-- $(loadEnv SearchPath) -- Loads entire PATH, may be slow | |||
load SearchPath [ "dd", "sha512sum" ] | |||
main = do | 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]]. | |||
</syntaxhighlight> | |||
Read | Read [[#Overrides]] if some packages are broken. | ||
=== Directly using cabal (no nix caching/reproducibility) === | === Directly using cabal (no nix caching/reproducibility) === | ||
[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 59: | Line 69: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
{{note|Some packages may need additional libraries/programs, notably <code>zlib</code>, you should be able to add them as additional programs in the nix-shell option.}} | |||
{{note|Since Cabal 2.0, cabal has acquired caching similar to nix (but not as powerful) and reproducibility (via the cabal.project file and the index-state option). See [https://cabal.readthedocs.io/en/latest/cabal-project-description-file.html#cfg-field-index-state] for more information.}} | |||
=== Using Stack (no nix caching) === | === Using Stack (no nix caching) === | ||
Similarly you can use | 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 86: | Line 95: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
{{note|For users of a stable version of NixOS there could be a problem where Stack tries to use a GHC version that is not yet in the given channel of Nixpkgs. Example at the time of writing: When using NixOS 23.05, Stack defaults to using the LTS-21.10 resolver, which uses <code>ghc-9.4.6</code>. However, the newest version of GHC in the 23.05 channel is <code>ghc-9.4.4</code>, thus Stack fails to execute some commands.<br/> | |||
As a solution, [https://docs.haskellstack.org/en/stable/yaml_configuration/#resolver-or-snapshot specify a resolver in your <code>stack.yaml</code>] file that uses a GHC version available for your channel. You can find a list of snapshots on https://www.stackage.org/snapshots. Or alternatively, set the resolver as a command line argument, which is required for running commands such as <code>stack new</code>.}} | |||
As a solution, [https://docs.haskellstack.org/en/stable/yaml_configuration/#resolver-or-snapshot specify a resolver in your <code>stack.yaml</code>] file that uses a GHC version available for your channel. You can find a list of snapshots on https://www.stackage.org/snapshots. Or alternatively, set the resolver as a command line argument, which is required for running commands such as <code>stack new</code>. | |||
=== Using developPackage (use the nix packages set for haskell) === | === Using developPackage (use the nix packages set for haskell) === | ||
Line 94: | 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 157: | 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 163: | 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 184: | 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 190: | 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 201: | 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 210: | Line 226: | ||
=== Using haskell-flake (flake-parts) === | === Using haskell-flake (flake-parts) === | ||
[https://community.flake.parts/haskell-flake haskell-flake] aims to simplify writing Nix for Haskell development through use of [ | [https://community.flake.parts/haskell-flake haskell-flake] is a project that aims to simplify writing Nix for Haskell development through use of [[Flake Parts|flake-parts module system]]. It uses <code>callCabal2nix</code> and <code>shellFor</code> under the hood while exposing friendly module options API. For an overview of Flakes, see the [[Flakes]] wiki page. | ||
* For existing Haskell projects, initialize with: | * For existing Haskell projects, initialize with: | ||
Line 225: | Line 241: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
This command will generate a project template with additional configuration details, comments, and examples. Below is an example minimal flake definition for a simple project: | |||
{{file|flake.nix|nix| | |||
<nowiki> | |||
{ | |||
inputs = { | |||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; | |||
flake-parts.url = "github:hercules-ci/flake-parts"; | |||
haskell-flake.url = "github:srid/haskell-flake"; | |||
}; | |||
outputs = inputs@{ self, nixpkgs, flake-parts, ... }: | |||
flake-parts.lib.mkFlake { inherit inputs; } { | |||
systems = nixpkgs.lib.systems.flakeExposed; | |||
imports = [ inputs.haskell-flake.flakeModule ]; | |||
perSystem = { self', pkgs, ... }: { | |||
haskellProjects.default = { | |||
# Project configurations such as GHC version and package overrides are defined here | |||
# See: https://flake.parts/options/haskell-flake | |||
}; | |||
# haskell-flake doesn't set the default package, but you can do it here. | |||
packages.default = self'.packages.example; | |||
}; | |||
}; | |||
} | |||
</nowiki> | |||
}} | |||
Once configured, you can build and run the project with: | |||
<syntaxhighlight lang="console"> | |||
$ nix build # Build the project; the binary will be placed in ./result/bin | |||
$ ./result/bin/example | |||
$ nix run # Alternatively, run the default executable directly | |||
</syntaxhighlight> | |||
or enter a development shell to use the standard development tools provided by the flake: | |||
<syntaxhighlight lang="console"> | |||
$ nix develop | |||
$ cabal run | |||
</syntaxhighlight> | |||
The build process will use the <code>example.cabal</code> file and run the executable defined within it. A current limitation is that if your Cabal file contains multiple <code>executable</code> blocks, you can only assign one as the default package. This limitation also applies when using a <code>cabal.project</code>. To run a specific executable by name, use the following command <code>nix run .#another-example</code>. Below is an example cabal file defining two executables: | |||
{{file|example.cabal|haskell| | |||
<nowiki> | |||
executable example | |||
main-is: Main.hs | |||
... | |||
executable another-example | |||
main-is: AnotherMain.hs | |||
... | |||
</nowiki> | |||
}} | |||
==== Further reading ==== | |||
* [https://github.com/srid/haskell-template/tree/master Example Haskell project with a development environment] | |||
* [https://github.com/srid/haskell-multi-nix/tree/master Example cabal.project multi-package Haskell project] | |||
* [https://community.flake.parts/haskell-flake/start Getting started with haskell-flake]. | |||
* [https://community.flake.parts/haskell-flake/dependency Overriding dependencies in a haskell-flake] | |||
* [https://flake.parts/options/haskell-flake haskell-flake haskell-flake options reference] | |||
== Overrides == | == Overrides == | ||
Line 322: | Line 403: | ||
== Limitations == | == Limitations == | ||
=== cabal2nix === | |||
When using the <code>cabal2nix</code> tool, Nix does not pull a cabal package by respecting the constraint specified in the cabal file (see [https://github.com/chrissound/Cabal2NixLimitationExample example]). Issue is discussed [https://stackoverflow.com/questions/57441156/pulling-in-specific-haskell-packages-cabal-dependencies-with-nix here]. You should be using <code>callCabal2nix</code> anyway. | |||
<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: | === IFD and Haskell === | ||
<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 == | |||
* '''Official Docs:''' [https://nixos.org/manual/nixpkgs/unstable/#haskell '''The Haskell section in the nixpkgs manual'''] | |||
* [https://nixos.asia/en/nixify-haskell-nixpkgs '''Nixifying a Haskell project using nixpkgs'''] explains how to use Nix to package and develop Haskell projects using nothing but nixpkgs. | |||
* [https://github.com/mhwombat/nix-for-numbskulls/blob/78bcc186f79931c0e4a1e445e2f6b1f12f6d46be/Haskell/ss-haskell-dev.md '''Super-Simple Haskell Development with Nix'''] (and [https://discourse.nixos.org/t/super-simple-haskell-development-with-nix/14287/2 discussion] that provides interesting alternative methods together with there pro and cons) | |||
* [https://discourse.nixos.org/t/nix-haskell-development-2020/6170 '''Nix Haskell Development (2020)'''] | |||
* [https://discourse.nixos.org/t/haskellpackages-stm-containers-fails-to-build/5416/4 '''How are Haskell packages managed in nixpkgs?'''] | |||
* [https://www.youtube.com/watch?v=KLhkAEk8I20 '''How to fix broken Haskell packages?''' (video)] | |||
[[Category:Languages]] | [[Category:Languages]] | ||
[[Category:Haskell]] |