Last active
May 21, 2022 20:52
-
-
Save drvink/3a2770610e014deac306 to your computer and use it in GitHub Desktop.
on nix: a language crash course for ml and haskell programmers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# locale bug workaround | |
[ -e "$HOME/.nix-profile/lib/locale/locale-archive" ] \ | |
&& export LOCALE_ARCHIVE="$HOME/.nix-profile/lib/locale/locale-archive" | |
# needed for GHC, Cabal, etc. to find stuff properly | |
# requires nix's patched ghc-paths package | |
if [ -e "$HOME/.nix-profile/bin/ghc" ]; then | |
export NIX_GHC="$HOME/.nix-profile/bin/ghc" | |
export NIX_GHCPKG="$HOME/.nix-profile/bin/ghc-pkg" | |
export NIX_GHC_DOCDIR="$HOME/.nix-profile/share/doc/ghc/html" | |
export NIX_GHC_LIBDIR="$HOME/.nix-profile/lib/ghc-$($NIX_GHC --numeric-version)" | |
fi | |
if [ -d "$HOME/.nix-profile" ]; then | |
export C_INCLUDE_PATH="$HOME/.nix-profile/include" | |
export LIBRARY_PATH="$HOME/.nix-profile/lib" | |
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Read this file first! | |
# This is the file we will be using as our entry point (i.e. | |
# "nix-env -f custom-packages.nix -i". | |
# Introduction | |
# ============ | |
# nix's syntax is not very friendly; while somewhat ML-like, it diverges in | |
# unfortunate ways that confuse both ML and curly brace language users. The nix | |
# manual is good enough at explaining how to do things like writing a simple | |
# "package", but fails to explain the syntax and semantics of the language, | |
# which leads to confusion: what does this magic stuff at the top of the file | |
# actually mean, and how does evaluation flow through a typical program? The | |
# goal of this document is to explain the operational semantics enough so that | |
# an ML or Haskell user will easily understand the code, allowing them to focus | |
# on the concepts, which we leave to the manual. The file also demonstrates a | |
# conceptually challenging override of the standard derivation-building function | |
# for Haskell packages; its understanding is left as an exercise for the reader. | |
# Nearly every common language construct is covered, but tersely. The following | |
# article goes into even further detail and is highly recommended reading: | |
# | |
# https://medium.com/@MrJamesFisher/nix-by-example-a0063a1a4c55 | |
# We will use # comments for our "literate nix", and C-style comments to | |
# annotate the purpose of the code. Without further ado, we will begin | |
# describing the language elements as we come across them. | |
# Functions and sets | |
# ================== | |
# A comma-delimited brace list followed by a colon is basically a function | |
# accepting a single record as its argument. The record will be destructured | |
# into name bindings a la pattern matching. Note that when constructing a | |
# record, the values are *semicolon*-delimited, and you must label each one as | |
# you would with a record. Like a record, the items can be in any order. | |
# Recursive records can be constructed by placing "rec" before the opening | |
# brace. nix calls records "sets", though they are much closer to immutable | |
# records than they are sets. As for mutability, there is none: nix has no | |
# equivalent to "mutable" or ref/IORef anywhere in the language. | |
# If not provided by the caller, a function can provide a default value for a | |
# set member by using the "name ? val" syntax as shown below. Since this is our | |
# entry point, we must declare defaults for all arguments. | |
{ pkgs ? (import <nixpkgs> {}) | |
, system ? builtins.currentSystem }: | |
# We are now in the body of the function, but first, we will explain... | |
# Imports and paths | |
# ================= | |
# "import" loads a nix expression from a file and returns its value. <name> | |
# uses the nix search path (the environment variable NIX_PATH) to provide a path | |
# corresponding to the key "name". Paths can also be directly specified; the | |
# following are all considered paths: | |
# | |
# ~/sup | |
# /homie | |
# ./hober | |
# http://nixos.org | |
# | |
# Note that paths are written EXACTLY as shown above--they are not quoted. Also | |
# note that despite the appearance of interchangability, paths and strings are | |
# distinct types with differing semantics. For example, if a path corresponding | |
# to a directory is passed to import, import will implicitly try to load the | |
# file "default.nix" from that directory. This expansion does not occur if a | |
# string is provided instead. Paths are not limited to use with just import; in | |
# fact, they have special interaction with regard to the nix datastore (the | |
# details of which we leave to the manual for explanation). | |
# In this case, the file that was imported is returning a function. We | |
# immediately call it with an argument of an empty set, which is used similarly | |
# to the unit type in ML. | |
# The builtins module is always present without needing any sort of import. | |
# "let" bindings | |
# ============== | |
# "let" is equivalent to let in Haskell. nix is lazy, so be careful to not | |
# introduce cycles. A let must be accompanied by a scoping "in"--there are no | |
# module-level bindings in nix. All of the following are valid: | |
# | |
# let add = { x, y }: x + y; in ... | |
# | |
# let x = | |
# let y = 1 + 2; | |
# z = 99; in | |
# y + z; | |
# in ... | |
# @ can be used to bind a name to a set passed to a function: | |
# | |
# let args@{ a, b }: ... | |
# This can be used to permit additional, unspecified members to be in the set | |
# (the ... below is literally an ellipsis): | |
# | |
# let args@{ a, b, ... }: | |
# Functions can also be curried and partially applied: | |
# | |
# let x = | |
# let add = a: b: a + b; | |
# add1 = add 1; in | |
# add1 2; # x == 3 | |
# in ... | |
# Scoping with "with" | |
# =================== | |
# "with expr" introduces the contents of expr (which is generally a set) into a | |
# lexical scope. It could apply to everything following it at the module level, | |
# like "open" in OCaml: | |
# | |
# { a, b, c }: | |
# with b; # *contents* of b are opened into everything below | |
# Or it can be used like OCaml's "let open Some_module in": | |
# | |
# let x = | |
# with a_set_containing_qqq; # | |
# let y = 1 + 2; | |
# z = 99; in | |
# y + z + qqq; | |
# in ... | |
# Trial and error is encouraged to get a feel for how it works. Since "import" | |
# is an expression, you can also write things like "with import ./a_path {}". | |
# More examples: | |
# | |
# let f = x: y: (with a_set; x + y); in ... | |
# let f = with a_set; { x = 1 }; in ... | |
# let f = x: y: with a_set; x + y; in ... | |
let | |
self = with pkgs; { | |
# Inherit (no relation to object-oriented stuff) | |
# ============================================== | |
# Inside a set, inherit is equivalent to "label = label"; e.g. if "label" is | |
# in our lexical scope and has a value of 1, then "label" in the set we're | |
# constructing will be 1. | |
inherit | |
cabal2nix | |
glibcLocales /* work around https://github.com/NixOS/nix/issues/599 */ | |
nix-prefetch-scripts; | |
mdl-haskell-env = | |
with pkgs.haskellPackages; | |
let | |
# Lists are space-delimited. ++ concatenates lists; nix has no cons | |
# operator. + concatenates strings or paths. Note that lists are not | |
# lazy! | |
pkgs = [ | |
attoparsec hashtables haskell-src-exts ListLike mono-traversable | |
optparse-applicative parsec prelude-extras process QuickCheck | |
regex-base regex-compat-tdfa regex-posix regex-tdfa split text vector | |
]; | |
tools = [ | |
alex cabal-install cpphs ghc-mod ghci-ng happy haskell-docs hscolour | |
]; in | |
ghcWithPackages (hp: pkgs ++ tools); | |
}; in | |
# All nix programs ("expressions") must eventually return either a "derivation" | |
# (a special type which describes something to be installed) or a set of | |
# derivations. Refer to the manual for an explanation of the derivation type. | |
self |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ pkgs }: | |
{ | |
callPackage = args: pkgs.lib.callPackageWith (pkgs // args); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This file is actually named "~/.nixpkgs/config.nix". It is imported by | |
# nixpkgs/pkgs/top-level/all-packages.nix. | |
{ pkgs }: | |
# The function will return a set. | |
{ | |
# A set element is of the form "name = value;". nix does not permit eliding | |
# semicolons anywhere one is required, similar to the syntax of BIND's | |
# named.conf. | |
allowUnfree = true; | |
# While there is nothing inherently special about a set member named | |
# "haskellPackageOverrides", ~/.nixpkgs/config.nix is the canonical place to | |
# put a number of different things; read all-packages.nix to see how it works. | |
# | |
# nix often uses a pattern where a function takes two sets as an argument and | |
# returns another set, where the intent is that the function's caller will | |
# merge the returned set with the set that was passed with the name "super". | |
# The name "self" is used to indicate the post-override set. Note that these | |
# names are simply convention; they are not special. Careless use of self can | |
# lead to infinite recursion. | |
haskellPackageOverrides = self: super: with pkgs.haskell.lib; { | |
# // gives the union of a set, with the RHS shadowing any names present in | |
# the LHS. | |
mkDerivation = args: super.mkDerivation (args // { | |
doCheck = false; | |
enableLibraryProfiling = true; | |
hyperlinkSource = true; | |
/* | |
* XXX HACK: The ghc-mod override in | |
* pkgs/development/haskell-modules/configuration-common.nix drags in a | |
* ton of extra packages by declaring emacs as an executable tool | |
* dependency; we work around it by avoiding compilation of the elisp | |
* files, but it would be preferable if we could compile them iff emacs is | |
* present on the machine. We have to do the override here instead of in | |
* the ghc-mod block because the problematic overrides are applied *after* | |
* the ones in the ghc-mod block. | |
*/ | |
executableToolDepends = | |
# "set.attr or val" returns attr if present, otherwise the RHS of "or". | |
# "or val" is optional; an exception will be raised if the attribute | |
# does not exist, and nix will terminate if the exception is not caught. | |
let xs = args.executableToolDepends or []; in | |
if args.pname != "ghc-mod" then xs else | |
# "builtins" is always present. | |
builtins.filter (x: x != pkgs.emacs) xs; | |
postInstall = | |
let pi = args.postInstall or ""; in | |
# '' introduces a multi-line string. nix-level variables can be | |
# interpolated with ${}. | |
if args.pname != "ghc-mod" then pi else '' | |
local lispdir=( "$out/share/"*"-${self.ghc.name}/${args.pname}-${args.version}/elisp" ) | |
# make -C $lispdir | |
mkdir -p $out/share/emacs/site-lisp | |
#ln -s "$lispdir/"*.el{,c} $out/share/emacs/site-lisp/ | |
ln -s "$lispdir/"*.el $out/share/emacs/site-lisp/ | |
''; | |
}); | |
ghc-mod = overrideCabal super.ghc-mod (drv: { | |
src = pkgs.fetchFromGitHub { | |
owner = "kazu-yamamoto"; | |
repo = "ghc-mod"; | |
rev = "8a2e490592d24c91198170b7ad047f0a24806f6f"; | |
# These weird-looking hashes are base32-encoded with nix-hash. | |
sha256 = "1mznkfrlf0kqir5aai6gs77n7dp7kcvccmplicf56812fjfyk1y6"; | |
}; | |
}); | |
ghci-ng = overrideCabal super.ghci-ng (drv: { | |
src = pkgs.fetchFromGitHub { | |
owner = "chrisdone"; | |
repo = "ghci-ng"; | |
rev = "738f66f3d1f1a3b7ba574fb9c83da793179a42c3"; | |
sha256 = "0flawgqdn01axkzac3zkzzqgbjm7z7b4y0xwcvr360x1fbnmmp52"; | |
}; | |
executableHaskellDepends = | |
let deps = with super; [ containers syb time transformers ]; | |
in drv.executableHaskellDepends ++ deps; | |
version = "0.0.0"; | |
homepage = "https://github.com/chrisdone/ghci-ng"; | |
description = "Next generation GHCi"; | |
}); | |
haskell-docs = overrideCabal super.haskell-docs (drv: { | |
src = pkgs.fetchFromGitHub { | |
owner = "chrisdone"; | |
repo = "haskell-docs"; | |
rev = "431e08e422c12f684712d4d8abd67ce0d73b9505"; | |
sha256 = "0s3zzn9p418n4a2kgrwc18mcmravwlag5c1djdi19lpj4k1qxk8j"; | |
}; | |
patches = [ ~/nix/patches/haskell-docs/0001-packagename-type-ambiguity.patch ]; | |
}); | |
}; | |
packageOverrides = super: let self = super.pkgs; in { | |
bitlbee = pkgs.lib.overrideDerivation super.bitlbee (drv: { | |
src = pkgs.fetchFromGitHub { | |
owner = "bitlbee"; | |
repo = "bitlbee"; | |
rev = "b6a3fbf3b94d1e7e7aee82375661fc0934fec48b"; | |
sha256 = "0zrs5ar1y8jdj3mjjz9gdznz28cpfis1hjy9b2yx9nqvm1im7mvi"; | |
}; | |
}); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment