Run Elm in the Terminal


Elm is usually run in a browser with output in HTML. So how does one run Elm outside the browser, say, in the terminal? Here's a quick setup.

Setup Headless Elm

A headless program is one without a UI. If we run in a terminal or the commandline, we'll be bypassing any UI building and just using the console to log out program results. The same kind of program could be run in node. In fact, when we exercise this program, we'll use node to run it.

Let's walk through a quick code snippet. Let's say that I wrote an elm program in MyProgram.elm. In this case, it's simply logging a value:

module MyProgram exposing (..)


print : Int -> Int
print num =
    Debug.log "num of destiny" num

Pretty interesting, eh? How should we run this? Let's write a program runner. In Main.elm, let's work through the different bits:

First, import your program that you want to run.

module Main exposing (..)

import MyProgram exposing (print)

Then import the program function from platform. This is the function that Elm provides to create a headless program:

import Platform exposing (program)

Now let's write the function that calls the program function and initializes it with sufficient setup data. First, the function signature:

nodeProgram : a -> Program Never () ()

Let's break it down:

  • nodeProgram is the name of our function
  • a indicates that this function can take any generic function
  • The return value is a Program type.
  • Program's first arg is Never, which means it is a program that will never receive flags. If you want flags, use the sister programWithFlags function.
  • Program's next two args are (), or unit. The first () is the model. This program runner has no state, so it has no model. The last () indicates there are no Cmds or effects that are handled by this program.

The actual body of the function and the call to program is like this:

nodeProgram _ =
    program
        { init = ( (), Cmd.none )
        , update = \() -> \() -> ( (), Cmd.none )
        , subscriptions = \() -> Sub.none
        }

Another quick breakdown:

  • The program takes one function parameter. This is to handle our MyProgram#print output. The _ naming convention indicates it's unused.
  • init and update keep the model at () and Cmd.none. These are required values in the program invocation, but we're not going to use them.
  • Sames goes for subscriptions

Finally, we get to run the module with a main function:

main : Program Never () ()
main =
    nodeProgram (print 9)

This is the entry point for our runner. It is also the place where we call our print function from our module and pass it to the headless nodeProgram.

Running Elm on Node

Now all we have to do is compile and run. To compile:

elm make --output main.js *.elm

And then to run:

node main.js

And as we'd expect from our setup, the output is:

num of destiny: 9

For the code all together, see this gist.

elm-run

Not too bad, right? Well it can get even easier. If you'd like to have this Main.elm setup wrapped up in a package that already exists, try out elm-run.

To install:

npm install -g elm-run

And then to run against your program:

elm-run MyProgram.elm

The output will be the same.

What are some other ways that you've found to run a headless Elm program?