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 + bThis is equivalent to this code in JavaScript:
function add(a, b) {
return a + b
}You can call this function as follows
> add 1 2
3Here we have yet another function with an if/else statement:
pluralize singular plural quantity =
if quantity == 1 then
singular
else
pluralWhich 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 leavesThe 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 != 0In 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 branchUnlike 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 * radiusElm Types and Type Annotations
Booleans:
True
False
-- equals
1 == 1
-- negation operator
not (1 == 1)
-- not equals
1 /= 2Strings:
-- 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 intFor 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 genericsYou can also use type annotations to denote higher-order functions:
prepend prefix word = prefix ++ word
prepend:string -> string -> stringAn 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 StringElm 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 == 1Lists
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 == 6You 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 + bWill evaluate as this:
<function> : number -> number -> numberWhich 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.reversewhere 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 somethingBadHappenedFor 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 |
NothingFor 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 [] -- NothingElm, 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: cucumberReferences

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.
