Skip to content

Instantly share code, notes, and snippets.

@greim
Last active March 5, 2018 18:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save greim/ac1e7271fd6d35279289533f1925c807 to your computer and use it in GitHub Desktop.
Save greim/ac1e7271fd6d35279289533f1925c807 to your computer and use it in GitHub Desktop.

Elm: Supplementary Notes

In which I get angry because some aspect of Elm seems weird to me, and the docs aren't helping, so I jot down these notes because writing forces me to think deeply about things in a way that I'm incapable of doing otherwise gaaasspp

Grokking JSON Decoders

Preliminary: We'll be dumping Json.Decode into our namespace to reduce typing, such that we have naked functions such as int, which is actually Json.Decode.int, and so forth:

import Json.Decode exposing (..)

Okay so first things first: a decoder is not a parser, but a way to take already-parsed JavaScript values like objects and numbers, and bring them into the statically-typed world of Elm, while accounting for all the crazy things that could happen en-route, like the value being null, or an object instead of a string, etc.

Often those JavaScript values originated from a JSON.parse() operation, but not always. For example, in event handling, when reading event objects (ev.target, ev.clientX, etc.) you'd use a JSON decoder, even though ev object never existed as a JSON string.

To actually do JSON-parsing, you pass a decoder to the decodeString function.

decodeString : Decoder -> String -> Result

Thus, you'll see something like this:

case decodeString int "42" of
  Ok n ->
    -- do something with n
  Error message ->
    -- something bad happened

decodeString isn't very interesting or complicated. From here on out, it becomes a question of building decoders, which as mentioned are descriptions of how to bring JavaScript values into your Elm program. Firstly, there's a set of ready-to-use primitive decoders, one for each Elm primitive type. Also, importantly, these are Elm primitive types, not JS primitive types. These include:

Json.Decode.int
Json.Decode.string
Json.Decode.bool
-- etc

Next, Elm docs talk about how simple decoders "snap together" to form more complex decoders. Namely, decoders for non-primitive types can't be used directly; they must first be parameterized by other decoders. For example list doesn't give us a decoder that's ready to use. We must first parameterize it, as in list string, which gives us a decoder that converts values into List String.

list string -- produces Result (List String)
list bool -- produces Result (List Bool)
list (list string) -- produces Result (List (List String))
-- and so forth

But what's this Result thing? Obviously, parsing JSON and converting arbitrary JavaScript values into Elm offers no guarantee of success. First of all the JSON string might be malformed, in which case decodeString fails. Or, it produces an unexpected JavaScript value, in which case the decoder fails. This is why these operations return a Result rather than returning a value directly. Thus, in order to compile, your program must handle both the Ok and Error cases of the Result type.

Furthermore, this is why you'll sometimes see Json.succeed, which is a sort of imposter decoder that ignores the JavaScript value handed to it and always just returns Ok on pre-specified value. It can then be passed in wherever a decoder is expected.

Building and using JSON decoders can get arbitrarily complicated, but this should provide enough of a conceptual foundation that the official docs can take over from here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment