VSCodium
with extensions and executables forHaskell
.- A sample
Haskell
project. - Several ways to run a
Haskell
app.
Spoiler
- NixOS wiki - Haskell
- flake.nix - code in this flake is extensively commented.
- codium-haskell - template for this flake.
- Haskell - general info about
Haskell
tools. - language-tools/haskell - a flake that provides
Haskell
tools. - codium-generic - info just about
VSCodium
with extensions. - Troubleshooting
- flakes - my Nix flakes that may be useful for you.
- Conventions
- Prerequisites
-
Install Nix - see how.
-
In a new terminal, start a devshell and run the app.
nix flake new my-project -t github:deemp/flakes#codium-haskell cd my-project git init && git add nix develop cabal run
-
Write
settings.json
and startVSCodium
.nix run .#writeSettings nix run .#codium .
-
Open a
Haskell
fileapp/Main.hs
and hover over a function. -
Wait until
Haskell Language Server
(HLS
) starts giving you type info.
The devShells.default
here is similar to the cabal shell.
The nix-managed
package (package in this flake) has several non-Haskell
dependencies.
First, as nix-managed
uses an lzma
package, it needs a C
library liblzma
. This library is delivered via Nix
as pkgs.lzma
.
Second, nix-managed
calls the hello
command at runtime (see someFunc
in src/Lib.hs
). This command comes from a hello
executable which is delivered via Nix
as pkgs.hello
.
cabal
from devShells.default
has on its PATH
that hello
executable.
Let's inspect what's available.
-
Enter the
devShell
.nix develop
-
Run the app.
cabal run
-
Next, access the
hello
executable in a repl.cabal repl ghci> :? ... :!<command> run the shell command <command> ... ghci> :! hello Hello, world!
-
ghcid
uses thecabal repl
command. That's why, when runningghcid
,hello
will be available to the app.ghcid
-
ghcid
will run not only themain
function, but also the code in magic comments (Seeapp/Main.hs
). -
Sometimes,
cabal
doesn't use theNix
-supplied packages (issue). In this case, usecabal v1-*
- commands.
Below is the comparison of ways to run a Haskell
app.
I prefer to use anything but stack
.
Advantages - incremental builds, can pass the build dependencies to devshell (See the PR)
Make a shell with all deps available and build incrementally via cabal
.
nix develop .#shellFor
Advantages - can build incrementally
Disadvantages - not that reproducible builds
Incremental builds via cabal
+ Nix
-provided packages. A devshell will run the app via cabal run
.
nix develop .#cabal
Advantages - reproducible build, make an executable binary app from a Haskell executable.
Disadvantages - no incremental builds (rebuilds the whole project from scratch on slight code changes)
Nix
provides necessary packages, binaries and libraries to the app. A devshell will run the app
nix develop .#binary
Advantages - reproducible lightweight container from a package
Disadvantage - no incremental builds
Put an executable into a Docker image and run it:
nix develop .#docker
Advantage - uses stack for incremental builds, very easy setup
Disadvantage - doesn't use Nix caches and takes packages from Stackage
nix develop .#stack
Suppose you'd like Nix
to supply a C
library liblzma to stack
using this integration.
You'd create a stack-shell
(more on that below) in flake.nix
and provide there a Nix
package pkgs.lzma
.
Then, stack
will create an isolated environment, where this library is present, and run your program in this environment.
In such an environment, your program won't have an access to other libraries and programs like rm
or git
.
But what if your program needs to call the rm
command?
In this case, your stack-shell
should contain the relevant package, pkgs.coreutils
.
This package will be turned into executables. Then, rm
and some other commands will become available in that isolated environment.
This sample Haskell
project demonstrates Stack
+ Nix
integration.
It has a Haskell lzma
package as a dependency (see package.yaml). This package depends on a C
library liblzma
.
Nix
delivers this library as a package pkgs.lzma
in stack-shell
.
There's also a pkgs.hello
package in stack-shell
.
This allows someFunc
from src/Lib.hs
to call the hello
as a shell command.
nix develop .#stack
stack run
This hello
executable will also be available in ghci
as a shell command:
stack ghci
ghci> :?
...
:!<command> run the shell command <command>
...
ghci> :! hello
Hello, world!
Furthermore, as ghcid
uses a stack ghci
command, you can run ghcid
as follows:
ghcid
Additionally, ghcid
will run the code in magic comments (See app/Main.hs
).
Necessary components of Stack
+ Nix
integration:
flake-compat
ininputs
offlake.nix
- This is to turn
stack-shell
inflake.nix
into a valid stack shell instack.nix
- repo
- This is to turn
- Nix enabled in
stack.yaml
stack.nix
- The file should have the same name as the value of
shell-file
instack.yaml
- The file should have the same name as the value of
stack-shell
with necessary derivations inflake.nix
- The name
stack-shell
is chosen arbitrarily - The name should be the same as the one used in
stack.nix
- The name
This flake uses GHC of a specific version (ghcVersion
).
nixpkgs
provides other GHC
versions.
Explore them in a repl:
nix repl
:lf .
-- use your system
ghcVersions.x86_64-linux
To switch to a specific GHC
version (let's call it <ghc>
):
- In
flake.nix
, change theghcVersion
value to<ghc>
. - If you're using
stack
, instack.yaml
, change theresolver
to match the<ghc>
version.
- package.yaml - used by
stack
orhpack
to generate a.cabal
- .markdownlint.jsonc - for
markdownlint
from the extensiondavidanson.vscode-markdownlint
- .ghcid - for ghcid
- .envrc - for direnv
- fourmolu.yaml - for fourmolu
- ci.yaml - a generated
GitHub Actions
workflow. See workflows. Generate a workflow vianix run .#writeWorkflows
. - [hie.yaml] - a config for hie-bios. Can be generated via implicit-hie to check the
Haskell Language Server
setup.