Prototyping with Runtime Errors in Elm28 November 2016
While I was listening to The Changelog podcast episode #198, I caught Chris Allen saying something along these lines:
Contrary to popular belief, the Haskell type system has some escape hatches. For example, you can put an error value in the definition of a function if you wanna say it has a type but you don’t have time to implement it right now.
This turns out to be a really powerful technique, because it means that I can work in terms of types alone without having written the code or implemented any of the functions and then test combining the functions together without executing them, just seeing, just asking my REPL what type would it be if I composed functions
h– given that I’ve only defined their types, I haven’t implemented them, evaluating them would produce a runtime error – tell me what happens. And then I can figure out if I’m figuring out the right design or a combination of functions that achieves what I want before I’ve done any real work.
Basically the idea is that the types are this kind of a way to step away from the specifics so that you could make certain that you’re thinking the right thoughts before you do all the work upfront.
Chris most probably meant something like this:
foo :: String -> [String] foo = error "Oh hai!" bar :: [String] -> [String] bar = error "Nope."
bar have their type signatures written out, but instead of having an actual implementation, they raise a runtime error. These functions can be then used with other functions, for example in the repl. I’ll show it in a second, but first let’s see how we would implement something like that in Elm.
As for the first approach, we’re going to directly translate Haskell to Elm. Instead of
error, we’re going to use
foo : String -> List String foo = Debug.crash "Oh hai!" bar : List String -> List String bar = Debug.crash "Nope."
This code does compile, but if we try to include it in the repl…1
> import Main exposing (..) /Users/rav/Projects/prototyping-with-runtime-errors/repl-temp-000.js:556 throw new Error( ^ Error: Ran into a `Debug.crash` in module `Main` on line 9 The message provided by the code author is: Nope.
Well, this is not good. To fix this, we need to add an argument to the definitions of the functions. This will stop Elm from immediately evaluating the right side of the function definition (and crashing our program).2
foo : String -> List String foo x = Debug.crash "Oh hai!" bar : List String -> List String bar x = Debug.crash "Nope."
Now we should be able to import the module, compose some functions and get some feedback from the repl and the compiler.
> import Main exposing (..) > baz = foo >> bar <function:_user$project$Repl$baz> : String -> List String
This works without a problem, since we’re composing
foo : String -> List String with
bar : List String -> List String. After doing the composition with
>>, Elm interprets
String -> List String.
baz = foo >> bar is equivalent to
baz x = bar (foo x). When we call
baz, Elm first calls
foo with the argument
x and then
bar with the result of
And now let’s try the other way around:
> bux = bar >> foo -- TYPE MISMATCH ----------------------------------- repl-temp-000.elm The right argument of (>>) is causing a type mismatch. 4| bar >> foo ^^^ (>>) is expecting the right side to be a: List String -> c But the right side is: String -> List String Hint: With operators like (>>) I always check the left side first. If it seems fine, I assume it is correct and check the right side. So the problem may be in how the left and right arguments interact.
The second definition,
bux, causes some problems. That’s because we’re trying to compose
bar : List String -> List String with
foo : String -> List String and the type of the return value of
List String) doesn’t match the type of the only argument of
Here lies the power of this technique. Without writing the implementation of
bar, we were able to get some feedback from the compiler and see if our ideas make sense in the grand scheme of things. To give you a “real world example”, you could have a big
update function in your Elm app and a plan to split it into smaller helpers. You could then prototype with runtime errors and check if the plan is going to pan out or if you forgot about something.
In general, this approach is a handy tool to have in your toolbox.
If you enjoyed this article, take a look at Type Bombs in Elm by Kris Jenkins. It describes an approach which is useful in an opposite situation – when you don’t know the exact type and would like the compiler to tell you what it expects there.
If you wish to load this snippet into the repl, you need to put it into a module first. The easiest way to do that is to put
module Main exposing (..)as the first line of the file, add the function definitions and save the file as
To get a better grasp of it, write down two versions of that function—one with an argument on the left side of the equation and one without it—and compile them with
elm make Main.elm --output main.js. Then open the JS file and see how their implementations differ. ↩