Barbarian Meets Coding
barbarianmeetscoding

WebDev, UX & a Pinch of Fantasy

9 minutes read

Elm - FP Web Dev

What is Elm?

Elm is a functional programming language used to build web applications which transpiles to JavaScript.

Why Should You Care About Elm?

Elm provides significant advantages over JavaScript:

  • Increased reliability: The Elm compiler catches common errors that happen in JavaScript like those caused by typos or type coertion, etc, at compile time. This translates in having no pernicious and hard to spot runtime exceptions, since these errors will prevent your code from transpiling.
  • Great developer experience: The Elm compiler produces awesome error messages that help you

Basic Elm Syntax

Comments:

-- single line comment

{- block comment
that can span multiple lines -}

Variables are immutable and can only be assigned (once) at the moment they’re declared:

x = 1
helloWorld = "hello world"
longstring = """
multiline strings
"""

This is how you define a function on Elm:

add a b = a + b

This is equivalent to this code in JavaScript:

function add(a, b) {
  return a + b
}

You can call this function as follows

> add 1 2
3

Here we have yet another function with an if/else statement:

pluralize singular plural quantity =
    if quantity == 1 then
        singular
    else
        plural

Which is equivalent to the following JavaScript:

function pluralize(singular, plural, quantity) {
  if (quantity === 1) return singular
  else return plural
}

There’s a couple of differences though. The else in Elm is required since the code above is evaluated as a single expression (like the ternary operator in JavaScript).

You can call this function in Elm as follows:

> pluralize "leaf" "leaves" 2
2 leaves

The parenthesis aren’t normally required to call functions but they are often used to disambiguate which arguments we’re passing to which functions.

Anonymous functions (arrow functions) look like this:

(\n -> n%2 /= 0)
-- in JavaScript
-- n => n%2 != 0

In addition to if/else statements, Elm also has case expressions similar in behavior to JavaScript switch statement (in reality they do pattern matching which we’ll explain later in detail):

case msg.operation of
     "DELETE_TODO" ->
        -- delete
     "ADD_TODO" ->
        -- add
     "UPDATE_TODO" ->
        -- update
     _ ->
        -- default branch

Unlike the switch statement there’s no fall through between cases and therefore no need for the break keyword.

let expressions allow you to define variables within a givin scope. This lets you write simpler expressions:

let
    pi = 3.1416
    radius = 20
in
    2 * pi * radius

Elm Types and Type Annotations

Booleans:

True
False

-- equals
1 == 1

-- negation operator
not (1 == 1)

-- not equals
1 /= 2

Strings:

-- a simple string
"hello world"

-- string concatenation evaluates to "hello world"
"hello" ++ " world"

-- convert to string
toString 10 == "10"

Type Annotations

Elm allows you to provide type annotations for your variables:

helloWorld: String;
helloWord = "hello world"

stars: Int;
stars = 22;

searchResult: {name: String, stars: Int}
searchResult = {name = "Vintharas/myrepo", stars: 1}

list: List string
list = ["ha", "ha", "ho"]

The Elm compiler will check these type annotations and make sure that they are followed within your code. This gets more interesting when you can define your own types through type aliases:

type alias SearchResult =
   {
       id: Int,
       name: String,
       stars: Int
   }

Which then you can reuse across your codebase:

model = {
    query: String
    ,results: List SearchResult
}

A common type in Elm would represent a message within the Elm architecture:

type alias Msg = {
    operation: String
    , data: Int
}

We can also use type annotations to represent functions:

add: Int Int -> Int
-- takes two ints and returns yet another int

For instance, the Elm Architecture view could be seen as follows:

view: Model -> Html Msg
-- view is a function that takes a Model as argument
-- and returns an Html of type Msg (whose events trigger messages of type Msg)
-- this is essentially generics

You can also use type annotations to denote higher-order functions:

prepend prefix word = prefix ++ word
prepend:string -> string -> string

An to denote union types, types defined as the union of other types:

type Bool = True | False
type Order = Ascending | Descending

-- Union types can also include generic types
type ColumnOrder = Ascending String
                 | Descending String

-- Which gets useful to describe messages in the
-- Elm architecture
type Msg = DeleteById Int
         | Update Int
         | Search String

Elm Data Structures/Collections. Records, Tuples and Lists

Records

A record is hash map or dictionary that is immutable. You define it like this:

record =
  { name = "rusty iron sword", weight = 10, length = 1.2 }

-- you can access its properties using dot notation
record.name == "rust iron sword"

Records support a similar feature to Object spread operator in JavaScript to update an existing record with new information (which results in a new record):

-- creating a new record with all the properties from sword and a new name "Krull"
newSword = {sword | name = "shiny iron sword"}

Tuples

A tuple is a collection of unnamed items that can have any type and is immutable:

tuple = (1, "and", 2)

-- you can use tuple destructuring to access the elements within a tuple
(first, and, second) = (1, "and", 2)
first == 1

Lists

A list is a collection of items with the same type that is immutable (like an immutable array):

[1, 2, 3 ]

Elm lists have interesting functions to work with collections. For instance a filter function:

isOdd n = n % 2 /= 0
List.filter isOdd [1, 2, 3, 4, 5] == [1, 3, 5]

-- this is equivalent to
List.filter (\n -> n % 2 /= 0) [1, 2, 3, 4, 5] == [1, 3, 5]

And the map function:

List.map (\n -> n*3) [1, 1, 1] == [3, 3, 3]

All these operations have no side effects, that is, the don’t change the original list but create a new one with the desired changes.

Elm Special Features

Partial Application

All functions within Elm are curried which allows us to use partial application to our hearts content:

add a b =
    a + b

add 1 2 == 3

-- an add5 function resulting of partially applying the add function
add5 =
    add 5

add5 1 == 6

You can clearly see this in the elm REPL which prints the type of any function that you describe. For instance, typing this function in the REPL:

> add a b = a + b

Will evaluate as this:

<function> : number -> number -> number

Which shows a curried funtion (the original took two integer arguments but the new function takes only one and returns a function that takes another integer).

The pipeline operator

Elm uses functions heavily. Functions that call other functions which in turn call other functions ad infinitum. This results in a code similar to this with subsequent calls wrapped in parenthesis:

List.reverse (List.filter (\n -> n < 4) [1, 2, 3, 4, 5])

A more succint and readable way to write an equivalent makes use of the pipeline operator:

[1, 2, 3, 4, 5]
  |> List.filter (\n -> n < 4)
  |> List.reverse

where the pipeline operator |> subsequently calls each function which the result of a previous function.

Pattern Matching

  • TODO: complete this section

The Elm Architecture

The Elm architecture is the way that you build web applications in Elm. It’s based on three pillars:

  • Model: The state of your application
  • View: A way to view the state of your application as HTML
  • Update: A way to update the way of your application

This establishes a cycle where:

user interaction -> message (command) -> Update (state) -> model -> View -> HTML (the user can interact with)

The Elm Development Environment

  • TODO: complete this section

The Elm Package Manager

  • TODO: complete this section

Elm and Browser APIs

A Note on Error Handling

Elm doesn’t throw runtime exceptions. How does it then let you handle runtime errors? Instead of throwing exceptions Elm provides the Result and Maybe types.

The Result type is a wrapper around an operation that can go well or result in an error:

-- pseudocode
type Result =
  Ok allThingsGood |
  Err somethingBadHappened

For instance, the String.Int function lets you parse Strings into integers. This operation can fail whenever we try to parse a string that doesn’t contain an integer:

String.Int "42" -- Ok 42
String.Int "Cucumber" -- Err "Cucumber is not an Int"

In order to be able to use the actual value within the Result type we need to take into consideration the possibility of an error happening which guides you toward writing less error-prone applications. A special case of Result is the Maybe type reserved to represent when a function may not return a value:

-- pseudocode
type Maybe =
  Just aValue |
  Nothing

For instance, we can use List.head to get the first element of a List. However, a list may be empty in which case we wouldn’t be able to get nothing. Instead of returning null or undefined like JavaScript could the List.head will return the Nothing type:

List.head [1, 2, 3] -- Just 1
List.head [] -- Nothing

Elm, DOM and Virtual DOM

Elm provides a series of functions that allow you to model the browser DOM which result in a virtual representation of the DOM or VDOM. This is how it looks:

ul [ class "ingredients" ]
     [ li [] [ text "100 grams sugar" ]
     , li [] [ text "2 oz white chocolate" ]
     ]

Parsing JSON in Elm

  • TODO: complete this section

For more information refer to Elm’s documentation about decoders.

Making AJAX requests in Elm

  • TODO: complete this section

Debugging

The Debug.log function is useful when debugging your code as it allows you to print arbitrary information into the browser console.log. It returns the same value that you pass in so you can use it in the same spot as an actual value within your programs:

value = "cucumber"

Debug.log "a message:" value
-- prints a message: cucumber

References


Jaime González García

Written by Jaime González García , dad, husband, software engineer, ux designer, amateur pixel artist, tinkerer and master of the arcane arts. You can also find him on Twitter jabbering about random stuff.Jaime González García