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.