Prasanna Natarajan

Learning Elm

I don’t know what got me into wanting to learn Elm, but I finally spent some time to do it.

It’s a great simple course from Pragmatic Studio. Their course covers the basics, but they build a web app over the course and I think it might serve well as a solid foundation if I decide to practice a lot of Elm for some days, which I plan to do.

These are the notes I took while going through the entire course. These might make zero sense to you. It’s more of a reference material for me.


Commandline

elm-package install
Install elm-core, elm-html, elm-virtual-dom packages and setup the app.

elm-reactor
On the fly compilation of elm to js/html. handy for small projects only. Like babel js.

elm-make Bingo.elm --output bingo.js
compiles elm to js.

elm-live Bingo.elm --open --output bingo.js
live compile elm to js.

elm-repl
like irb

$ elm-live Bingo.elm --open --debug --output bingo.js
Debugger window to see the commands and msgs being sent while interacting with the app, and see the current model data. Just like the redux chrome plugin.


Doubts

Don’t know how to do a sortWith using a list of records.


Notes

Running elm app ‘fullscreen’ vs within a specific element

<script>
  Elm.Bingo.fullscreen();
</script>
<script>
  var node = document.getElementById('elm-app');
  var app = Elm.Bingo.embed(node);
</script>

Left to right vs Right to left expression evaluation

This is similar to yield self. Note the pipe operator.

main = 
  Html.text (String.repeat 3 (String.toUpper "Hi Prasanna!"))
main =
  "Hi Prasanna!" 
    |> String.toUpper 
    |> String.repeat 3 
    |> Html.text

Anon functions

Begins with backslash. Arrow instead of ‘=’.

-- named
add x y = x + y

-- anon
\x y -> x + y

-- anon assigned to variable
sum = \x y -> x + y

Anon functions can be used in higher order functions.

> String.filter
<function> : (Char -> Bool) -> String -> String

> String.filter (\c -> c == 'E') "hEllo prasanna byE"
"EE" : String

Qualified imports vs unqualified imports

-- Qualified
import Html
Html.text "blah"

-- Unqualified
import Html exposing (text, h2)
text "blah"

-- Unqualified import all
import Html exposing (..)
text "blah"

Defining scoped variables using “let..in..”

Within a function, you can define variables that will be used in the function using let in. Vars defined inside “let” is only accessible within “in”.

viewPlayer name gameNumber =
    let
        playerInfoText =
            playerInfo name (toString gameNumber)
                |> String.toUpper
                |> text
    in
        h2 [ id "info", class "classy" ]
            [ playerInfoText ]

Function with 0 arguments

… are actually constants in Elm. There are no 0-arg functions.

In truth, all Elm functions take exactly one argument and return a result. That result can be another function. (currying).


Type system

Type variables.

>  ['a', 'b']
['a','b'] : List Char

> []
[] : List a

where a is a type placeholder variable.

Type of a list of list of chars? List (List Char):

> [['a', 'b'], ['c', 'd']]
[['a','b'],['c','d']] : List (List Char)

Tuples and Records

Lists can contain only elements of same type. To have mixed types, use tuples:

> ('a', "pras", 1, 2.3, True)
('a',"pras",1,2.3,True) : ( Char, String, number, Float, Bool )

Records are key-value pairs. Like ruby hash.

> a = {name = "pras", age = 31}
{ name = "pras", age = 31 } : { age : number, name : String }
> a.name
"pras" : String
> a.age
31 : number

Type annotations

A function or a variable (or constant?) assignment can have an annotation in the line above it.

Why is the annotation a series of arrows, instead of commans and arrows?

> String.pad
<function> : Int -> Char -> String -> String

pad takes 3 args and returns 1 String. Shouldn’t the annotation be: Int, Char, String -> String?

No. Pad is actually a function that takes only 1 arg. So is all fns in Elm. Pad takes 1 arg (Int) and returns a function that takes a Char and returns a function that takes a String and returns a String!

Int -> (Char -> (String -> String))

Piping with pipe operator |> is useful to chain curried functions.

Left to right evaluation. Normal usage.

> String.pad 20 '*' (String.repeat 3 "WoW")
"******WoWWoWWoW*****" : String

Right to left evaluation. Intuitive. With pipe.

> "WoW" |> String.repeat 3 |> String.pad 20 '*'
"******WoWWoWWoW*****" : String

Convert a list of names to starred versions using both ways:

> names = ["WoW", "BoB", "DoD"]
["WoW","BoB","DoD"] : List String

> starred name = name |> String.repeat 3 |> String.pad 20 '*'
<function> : String -> String

R-L:

> List.map starred names

L-R:

> names |> List.map starred

Accessing items in record

2 ways. 1 normal dot-notation, the other, a weird way.

> user = { name = "Wilma", age = 25 }
{ name = "Wilma", age = 25 } : { age : number, name : String }
> user.name
"Wilma" : String
> .name user
"Wilma" : String
> .name
<function> : { b | name : a } -> a

As seen above, .name is a special function that behaves like this anon fn: \obj -> obj.name.


Type vs Type aliases

You define your own new (custom) types in Elm using the type keyword.

type Pet = Cat | Dog | Bird

You can use a case of to pattern match a type.

Sometimes you want to give a name to a type that already exists. That’s where type alias is useful.

type alias Person = { name : String, age : Int, married : Bool }

The elm architecture

Model - represents a data structure (in our Bingo app, it’s a record) that holds state of the app. Update - a function that takes a message (with or without args) and an existing model and returns an updated new model. View - functions that help in rendering the model as html.

User interaction in the html objects, send a message. That message goes to the update function along with the existing model and a new updated model is created (and replaces the old model), and the view functions automatically render the affected html “reactively”.


Effects and Commands

wtf. Used to guarantee that Elm update functions are pure. Same input, same output.

Commands are used to generate random numbers outside of the pure functions, make http calls etc.

To generate a random number for a model’s field, you’d do this:

    case msg of
        NewRandom randomNumber ->
            ( { model | gameNumber = randomNumber }, Cmd.none )
-- in the update fn
    case msg of
        -- update game number and mark all entries as marked: false
        NewGame ->
            ( { model | entries = initialEntries }
            , generateRandomNumber
            )

-- elsewhere

generateRandomNumber : Cmd Msg
generateRandomNumber =
    --Random.generate (\num -> NewRandom num) (Random.int 1 100)
    Random.generate NewRandom (Random.int 1 100)

where Random is an imported module.

To render an Elm program with cmd/msg/effects, you need to use Html.program instead of Html.beginnerProgram:

main : Program Never Model Msg
main =
    Html.program
        { init = ( initialModel, generateRandomNumber )
        , view = view
        , update = update
        , subscriptions = (\_ -> Sub.none)
        }

always fn

Notice above the anon fn (\_ -> Sub.none). It never uses the passed in arg, and always returns the same Sub.none. It can be simplified as: always Sub.none. This will return a function that always returns Sub.none. Another usage with List:

> List.map (always 0) [1, 2, 3]
[0,0,0] : List number

(!) Infix operator to generate a tuple containing a model and a cmd

An update fn returns a tuple containing a model (record) and a cmd. It’s usually written like so:

            ( { model | entries = initialEntries }
            , generateRandomNumber
            )

But it can be written like so:

{ model | entries = initialEntries } ! [ generateRandomNumber ]

It’s definition in docs is:
(!) : model -> List (Cmd msg) -> (model, Cmd msg)


Result

http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Result

type Result error value = Ok value | Err error

The Result type can either be an Ok or an Err function that each take an arg.

> Ok
<function> : value -> Result.Result error value
> Err
<function> : error -> Result.Result error value
> Ok 4
Ok 4 : Result.Result error number
> Err 4
Err 4 : Result.Result number value

Eg: sqrt 4 will give 2. But what if the argument value came from a http request. It could be the actual value 4 or an error like “bad input”. We need to pass this arg to sqrt safely. We can’t pass it directly as it’s a type mismatch.

Here are the 2 results we have: one is an Ok and the other is an Error.

> res1
Ok 4 : Result.Result error number
> res2
Err "bad input" : Result.Result String value

Passing this directly to sqrt:

> sqrt res1
==================================== ERRORS ====================================

-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm

The argument to function `sqrt` is causing a mismatch.

4|   sqrt res1
          ^^^^
Function `sqrt` is expecting the argument to be:

    Float

But it is:

    Result error number

So we need to call it via Result.map http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Result#map:

> Result.map sqrt res1
Ok 2 : Result.Result error Float
> Result.map sqrt res2
Err "bad input" : Result.Result String Float

Http

The actual Http methods like Http.getString, Http.get, Http.post, Http.request don’t really send a http request. They are just Request a types.

To actually send those requests, use Http.send. It’s definition is:
send : (Result Error a -> msg) -> Request a -> Cmd msg

It takes 2 args and returns a Cmd msg that the runtime will receive and make http.

2nd arg is a Request. It can be one of those Http., like Http.getString url.

1st arg is a fn that takes a Result type and returns a msg, which is then used by the send fn to finally return a Cmd msg. The twist is, we already have a fn that takes a result and returns a msg. It’s the “NewEntries” Msg type defined as: type Msg = NewEntries (Result Http.error String).

So we just hvae to pass it.

Http.send NewEntries (Http.getString url)

So, that’s the tiring mechanism by which http requests are made.

JSON

Json strings need to be converted to elm types - either native int/string or custom types like Entry, User.
To do so, we use decoders.

Here’s a json string and a decoder that helps extract the “id” key’s value:

> import Json.Decode exposing (..)
> json = """{ "id": 1, "name": "pras" }"""
"{ \"id\": 1, \"name\": \"pras\" }" : String
> idDecoder = field "id" int
<decoder> : Json.Decode.Decoder Int
> id = decodeString idDecoder json
Ok 1 : Result.Result String Int

Compose a decoder from other decoders

Given this Entry elm type record, we need to extract an entry record from the given json.

type alias Entry = {id : Int, phrase : String, points : Int}
json = """ {"id": 1, "phrase": "pras", "points": 345}"""

The individual decoders first:

> idDecoder = field "id" int
<decoder> : Json.Decode.Decoder Int
> phraseDecoder = field "phrase" string
<decoder> : Json.Decode.Decoder String
> pointsDecoder = field "points" int
<decoder> : Json.Decode.Decoder Int

They can be used to extract the individual field’s value like so:

> phrase = decodeString phraseDecoder json
Ok "pras" : Result.Result String String

And these 3 encoders can be combined together to created the decoder for Entry.

> entryDecoder = map3 Entry idDecoder phraseDecoder pointsDecoder
<decoder> : Json.Decode.Decoder Repl.Entry

map3’s syntax: takes 4 args: a fn and 3 decoders and returns a combined decoder. The function takes the 3 values from the 3 decoders and returns the value.

Now this decoder can be used to decode the json into the elm entry type:

> prasEntry = decodeString entryDecoder json
Ok { id = 1, phrase = "pras", points = 345 } : Result.Result String Repl.Entry

List of records

Suppose you have an entryDecoder that returns an Elm record type, but the json has an array of entries, use the Decode.list function to generate a list of entries decoder: Decode.list entryDecoder.

If the json is like this:

{ 
  all: {
    the: {
      entries :
        [ ... ]
    }
  }
}

then, the decoder can be targeted at the specific place like this: Json.at ["all", "the", "entries"] (Decode.list entryDecoder).

elm-decode-pipeline instead of the default json Decoders

This plugin from NoRedInk allows us to decode json easily. No need to worry about map3, map5 etc.

This,

Decode.map4 Entry
  (field "id" Decode.int)
  (field "phrase" Decode.string)
  (field "points" Decode.int)
  (succeed False)

could be written as,

import Json.Decode.Pipeline as JDecode exposing (decode, required, optional, hardcoded)

    JDecode.decode Entry
        |> JDecode.required "id" Decode.int
        |> JDecode.required "phrase" Decode.string
        |> JDecode.optional "points" Decode.int 100
        |> JDecode.hardcoded False

Maybe it’s just a value or maybe it’s nothing!

type Maybe a = Just a | Nothing

If your function can return a value or nothing, then use the Maybe type and return either a just value or Nothing.

Eg: given an int, the toValidMonth will return the int if it’s a valid month, or Nothing.

toValidMonth : Int -> Maybe Int
toValidMonth month =
    if month >=1 && month <= 12 then
        Just month
    else
        Nothing

And a maybe result can be used in a case of statement like this:

maybeMonth = toValidMonth 12
case maybeMonth of
    Just value ->
      _ = Debug.log "valid" value
    Nothing ->
      _ = Debug.log "Nothing!"

Another example. List.head takes a list and returns a first element if it’s there or nothing if the list is empty.

> List.head [1, 2]
Just 1 : Maybe.Maybe number
> List.head []
Nothing : Maybe.Maybe a