chriswarbo-net: da9eaf5e947022bf5953450df581fe304c4b602c
1: ---
2: title: Compile-time I/O
3: ---
4:
5: I think [Gabriel Gonzalez' ideas about removing runtime I/O](
6: http://www.haskellforall.com/2017/10/why-do-our-programs-need-to-read-input.html) actually
7: map very closely to the [Nix](http://nixos.org/nix) language. Nix is a pure
8: functional programming language, with no I/O, where a program is an
9: "expression", which we can "evaluate". Some expressions evaluate to, for
10: example, an integer. Others evaluate to a boolean. The most interesting are
11: those which evaluate to a "derivation".
12:
13: A derivation is basically a datastructure containing an "environment" (a map
14: from strings to strings), a "builder" (a string containing a filesystem path),
15: "arguments" (a list of strings) and a set of "dependencies" (other
16: derivations). As far as the Nix language is concerned, these derivations are
17: just values like any other.
18:
19: By itself this is pretty useless, but there is a tool (`nix-build`) which will
20: take the result of evaluating a Nix program and, if it's a derivation, will
21: "build" that derivation. To build a derivation, we first build all of its
22: dependencies (recursively), then we run the "builder" as a program, with the
23: "arguments" passed as commandline arguments, and the "environment" as its env
24: vars. We also add an "out" variable to the environment, with a path as its
25: value, and the result of the build is whatever the builder wrote to that path.
26:
27: So why is this relevant? Firstly, the Nix language is useful for *describing*
28: what to do without *actually* doing anything. This is actually the same idea as
29: Haskell's I/O system: Haskell is a pure language, with no I/O, which calculates
30: a single value called `main`. That value has type `IO ()` which basically means
31: "a program which can perform arbitrary effects". A 'separate tool' (the Haskell
32: runtime system) takes the resulting value of `main` and runs it as a
33: program. Hence Haskell is like a pure meta-language, used to construct impure
34: programs.
35:
36: Elliott actually [makes this analogy](http://conal.net/blog/posts/the-c-language-is-purely-functional)
37: by comparing `IO ()` values to C programs, and the Haskell language to the C
38: preprocessor!
39:
40: So what practical effect does this way of thinking have, if any? I can't speak
41: for Dhall (Gabriel's language), but in the case of Nix there is a clear
42: distinction between "eval time" and "build time". I think this is the key to
43: figuring out how Gabriel can (provocatively) claim to 'perform all I/O at
44: compile time': the I/O happens during *evaluation*, which is basically like an
45: interpreter (if you're wondering why a compiler would implement an interpreter,
46: consider that "inlining a function" is basically an elaborate way to "call a
47: function" at compile time; and "constant folding" is an elaborate way to
48: "perform calculations" at compile time; the logical conclusion to doing this is
49: a "supercompiler", which can run arbitrary code at compile time).
50:
51: So really, Gabriel is saying we should embrace metaprogramming, to push as many
52: failure cases as possible into a run-at-compile-time language, so that the
53: resulting program (if compilation succeeds) is guaranteed to avoid those
54: problems. This is similar in spirit to static typing (catching errors before
55: running the program), although we're actually shifting the emphasis: "the
56: program" is actually rather trivial, since most of our code is part of the
57: compile-time language (basically, really extensive macros).
58:
59: I have sympathy for this; although there's a need to more precisely distinguish
60: between the "resulting program" and "the output of the resulting program". If we
61: perform a bunch of elaborate compilation which results in the number `42`, then
62: that is "a program which performs no I/O", but it's also not a particularly
63: interesting case. If the result of our compilation is a function, which
64: e.g. counts the number of words in a given string, then that itself might
65: perform no I/O, but it's not actually useful until it's invoked by something
66: which does: either a 'separate tool' (the equivalent of nix-build or Haskell's
67: RTS) or a subsequent compilation which "imports" that function.
Generated by git2html.