Haskell: Difference between revisions

imported>Tobias.bora
No edit summary
imported>Tobias.bora
No edit summary
Line 34: Line 34:
=== 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 `./my-script.hs`:
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>:
<pre>
<pre>
#!/usr/bin/env nix-shell
#!/usr/bin/env nix-shell
Line 47: Line 47:


=== Directly using cabal (no nix caching/reproducibility) ===
=== Directly using cabal (no nix caching/reproducibility) ===
Note that cabal is the basic Haskell tool used to configure builds and is internally used by all the Haskell's packaging methods (including slack 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:
Note that 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:
<pre>
<pre>
$  nix-shell -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ cabal-install ])"
$  nix-shell -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ cabal-install ])"
Line 56: Line 56:
Hello, Haskell!
Hello, Haskell!
</pre>
</pre>
Note that 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.


=== Using Stack (no nix caching) ===
=== Using Stack (no nix caching) ===
Line 70: Line 72:
You can also use the features offered by stack to enable nix integration in order to use nix to install the non-haskell dependencies. You can read more [https://docs.haskellstack.org/en/stable/nix_integration/ here].
You can also use the features offered by stack to enable nix integration in order to use nix to install the non-haskell dependencies. You can read more [https://docs.haskellstack.org/en/stable/nix_integration/ here].


===  Using developPackage ===
===  Using developPackage (use the nix packages set for haskell) ===


You can use also nix in place of stack to keep track of the dependencies in a reproducible way. 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 use also 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):
<pre>
<pre>
$ nix-shell -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ cabal-install ])" --run "cabal init"
$ nix-shell -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ cabal-install ])" --run "cabal init"
Line 87: Line 89:
}
}
</syntaxhighlight>
</syntaxhighlight>
(You can find a list of options and documentation for <code>developPackage</code> in [https://github.com/NixOS/nixpkgs/blob/0ba44a03f620806a2558a699dba143e6cf9858db/pkgs/development/haskell-modules/make-package-set.nix#L230 pkgs/development/haskell-modules/make-package-set.nix], note that it is a wrapper around <code>callCabal2nixWithOptions</code> with some additional functions to setup a development shell.)


Then you can build and run the program using:
Then you can build and run the program using:
Line 99: Line 103:
</pre>
</pre>


Nix will automatically read the <code>build-depends</code> field in the <code>*.cabal</code> file to get the name of the dependencies and use the haskell packages provided in the haskell packages configured in nix. Note that some of the packages present in the nix repository are broken (for instance because a package requires an older version of a library while nix only provides a recent version). For this reason it may be necessary to override some packages present in the nix package set.
Nix will automatically read the <code>build-depends</code> field in the <code>*.cabal</code> file to get the name of the dependencies and use the haskell packages provided in the configured package set provided by nix. Note that some of the packages present in the nix repository are broken (for instance because a package requires an older version of a library while nix only provides a recent version). For this reason it may be necessary to override some packages present in the nix package set as described below using the <code>overrides</code> and <code>source-overrides</code> attribute. Note that the <code>source-overrides</code> attribute can also turn out to be useful to load local libraries:
 
<syntaxhighlight lang="nix">
let
  pkgs = import <nixpkgs> { }; # pin the channel to ensure reproducibility!
in
pkgs.haskellPackages.developPackage {
  root = ./.;
  source-overrides = {
    mylibrary = ./mylibrary;
  };
}
</syntaxhighlight>
 
However as I understand I guess that you will not be able to enter the shell before `mylibrary` fully compiles… hence the need for `shellFor` to work simultaneously on multiple projects.
 
You can also get more details in [https://srid.ca/haskell-nix this tutorial] or in [https://github.com/NixOS/nixpkgs/blob/0ba44a03f620806a2558a699dba143e6cf9858db/pkgs/development/haskell-modules/make-package-set.nix#L230 pkgs/development/haskell-modules/make-package-set.nix].
 
===  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.


You can get more details in [https://srid.ca/haskell-nix this tutorial].
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 a the path of the project to compile it), and then to use `haskellPackages.shellFor {packages= p: [p.myproject1 p.myproject2]}` to create a shell with all wanted packages.
 
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>
packages:
  frontend/
  backend/
</syntaxhighlight>
 
Finally create a file <code>shell.nix</code> containing:
 
<syntaxhighlight lang="nix">
with import <nixpkgs> {};
# We add our packages to the haskell package set
(haskellPackages.extend (haskell.lib.compose.packageSourceOverrides {
  frontend = ./frontend;
  backend = ./backend;
}))
# We call on this set shellFor to drop us into a shell containing the dependencies of frontend and backend:
  .shellFor {
    packages = p: [p.frontend p.backend];
    withHoogle = true;
    buildInputs = [ pkgs.python pkgs.cabal-install ];
  }
</syntaxhighlight>
 
then you can use cabal to develop incrementally your projects using for instance:
 
<syntaxhighlight lang="bash">
$ nix-shell
$ cabal new-build all
</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>:
<syntaxhighlight lang="nix">
with import <nixpkgs> {};
# We add our packages to the haskell package set
(haskellPackages.extend (haskell.lib.compose.packageSourceOverrides {
  frontend = ./frontend;
  backend = ./backend;
})).backend
</syntaxhighlight>
 
=== haskell.nix (for complexe projects) ===
 
The [https://github.com/input-output-hk/haskell.nix haskell.nix] project allows you to have maximum flexibility (to create your own package set or use in teams with diverse people, some of them using stack, other using cabal, other using nix…). But this comes at the price of an additional complexity.


== Overrides ==
== Overrides ==


Since nixpkgs tries to maintain a single Sometimes packages are broken.
Since nixpkgs tries to maintain a single package set (based on the package set of stackage, while the remaining packages are picked from the latest version on Hackage) instead of using a solver to meet all version constraints for a specific project, it turns out that sometimes packages are broken (they can also be broken for various other reasons). However, you may be able to unbreak this package yourself.
 
The first thing to check to try to unbreak a package is to check which GHC version is compatible with the package you want to use. You are maybe using a too old version… or too new. You can change the version of ghc using <code>haskell.packages.ghcXYZ</code> in place of <code>haskellPackages</code> as explained above.
 
Then, you will surely need to change some packages. If you are using <code>developPackage</code> as explained above you can either use a normal override that will be described below or a simpler source override to override only the source as:
 
<syntaxhighlight lang="nix">
pkgs.haskellPackages.developPackage {
  root = ./.;
  source-overrides = {
    # Let's say the GHC haskellPackages uses 1.6.0.0 and your test suite is incompatible with >= 1.6.0.0
    HUnit = "1.5.0.0";
  };
}; 
</syntaxhighlight>
 
You can provide to <code>source-overrides</code> either:
* a version number (it will be [https://github.com/NixOS/nixpkgs/blob/b794e83b395253fc46df958a0a12e1fc7b80d069/pkgs/development/haskell-modules/lib/compose.nix#L48 forwarded internally] to <code>callHackage</code>, note that you do not need to specify any hash as nix is using a package <code>all-cabal-hashes</code> that contains all the cabal hashes, see <code>callHackageDirect</code> below if your package is not yet in <code>all-cabal-hashes</code>)
* a path (that will be forwarded internally to <code>callCabal2nix</code>), you can use the usual fetchers like <code>fetchurl</code> or <code>fetchFromGitHub</code> to generate that path, or a local path if you want to use a local library.
 
You can also use the more powerful override system to change any property of the derivation. This works for instance with <code>developPackage</code>:
<syntaxhighlight lang="nix">
pkgs.haskellPackages.developPackage {
  root = ./.;
  overrides = self: super: { # self is the new package set, super is the old package set
    random = pkgs.haskell.lib.overrideCabal super.random {
      version = "1.1";
      sha256 = "sha256-txikEFfiWjpx32k6sP4iY9SS51lnmzwv6m6jOxcdOlo="; # Use an empty string before knowing the hash
      doCheck = false;
    };
  };
}; 
</syntaxhighlight>
 
but you can also use overrides with <code>ghcWithPackages</code>. This example will for instance create a nix-shell where the library quipper is available:
<syntaxhighlight lang="nix">
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
pkgs.mkShell {
  buildInputs =
    let
      # Quipper does not work with GHC 7.10 or 8.10. The versions currently supported are GHC 8.0, 8.2, 8.4, 8.6, and 8.8.
      myHaskell = pkgs.haskell.packages.ghc884.override {
        overrides = self: super: {
          # fixedprec needs random 1.1 or below
          random = pkgs.haskell.lib.overrideCabal super.random {
            version = "1.1";
            sha256 = "sha256-txikEFfiWjpx32k6sP4iY9SS51lnmzwv6m6jOxcdOlo=";
          };
          fixedprec = haskell.lib.markUnbroken super.fixedprec;
          quipper = haskell.lib.markUnbroken super.quipper;
        };
      };
    in
      [
        (myHaskell.ghcWithPackages (hpkgs: [
          hpkgs.quipper
        ]))
      ];
}
</syntaxhighlight>
 
 
Note that <code>overrideCabal</code> takes as input the old package and the new attributes of the new package and outputs the new package.
 
Because some operations are very common, there exists some functions that call <code>overrideCabal</code> for you. For instance if you only want to disable checks and test suits for a package you can do <code>mypackage = pkgs.haskell.lib.dontCheck super.mypackage</code> and the above code also shows how to mark a package as unbroken. These functions are listed and documented in [https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib/compose.nix pkgs/development/haskell-modules/lib/compose.nix].
 
You can also use <code>callHackageDirect</code> (source and documentation [https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/make-package-set.nix#L196 here], you can see that it is a wrapper around <code>callCabal2nix</code>) to create a package using the hackage repository:
 
<syntaxhighlight lang="nix">
myHaskell = pkgs.haskellPackages.override {
  overrides = self: super: {
    mypackage = self.callHackageDirect {
      pkg = "mypackage";
      ver = "0.1.2.3";
      sha256 = ""; # The first time it will give you an error, replace the "" with the hash given in the error
    } {};
  };
};
</syntaxhighlight>
 
Finally, if your package is not in hackage, you can simply use [https://github.com/NixOS/nixpkgs/blob/0ba44a03f620806a2558a699dba143e6cf9858db/pkgs/development/haskell-modules/make-package-set.nix#L228 <code>callCabal2nix</code>], or the more advanced [https://github.com/NixOS/nixpkgs/blob/0ba44a03f620806a2558a699dba143e6cf9858db/pkgs/development/haskell-modules/make-package-set.nix#L212 callCabal2nixWithOptions]:
 
<syntaxhighlight lang="nix">
mypackage = self.callCabal2nix "mypackage" /path/to/package/or/fetcher {};
</syntaxhighlight>
 
This can be useful also when your package needs some libraries.


== Limitations ==
== Limitations ==


When using <code>cabal2nix</code>, 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 `callCabal2nix` anyway.
When using the (deprecated) tool <code>cabal2nix</code>, 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 `callCabal2nix` anyway.


== IFD and Haskell ==
== IFD and Haskell ==
Line 117: Line 274:
* <code>nix flake show</code>
* <code>nix flake show</code>
* <code>nix flake check</code>
* <code>nix flake check</code>
== Haskell.nix ==
<code>haskell.nix</code>[https://github.com/input-output-hk/haskell.nix] can automatically translate your Cabal or Stack project and its dependencies into Nix code.


[[Category:Languages]]
[[Category:Languages]]
[[Category:Applications]]
[[Category:Applications]]