Reactivity

In this section, we will see how that fits into a system that talks to servers, uses websockets, writes to databases, etc. The following diagram gives a high-level overview:

The main idea is that the vast majority of our application logic can be described in terms of the Elm Architecture, allowing us to make the most of stateless functions and immutability. In this realm we use Signals to route events to the right place.

When we want to have some effect on the world, we create a service totally separate from our core logic. Each service may be in charge of a specific task, like managing database connections or communicating via websockets. We script all of these effects with Tasks.

We get a lot of benefits by cleanly separating out these services. Most obviously, testing is much simpler. We can make sure our UI and application logic work without doing crazy tricks to mock a database.

Tasks make it easy to describe asynchronous operations that may fail, like HTTP requests or writing to a database. Tons of browser APIs are described as tasks in Elm:

• elm-http — talk to servers
• elm-history — navigate browser history
• elm-storage — save info in the users browser

Tasks also work like light-weight threads in Elm, so you can have a bunch of tasks running at the same time and the runtime RTS will hop between them if they are blocked.



port runner : Task x ()
port runner = getCurrTime andThen putCurrTime

main = ...

getCurrTime : Task x Time
putCurrTime : Time ...


First, notice the infrequently-used backtick syntax which lets us treat normal functions as infix operators.

Okay, now that we know that andThen is a normal function that takes two arguments, let’s see the type.

 andThen : Task x a -> (a -> Task x b) -> Task x b

The first argument is a task that we want to happen, in our example this is getCurrentTime. The second argument is a callback that creates a brand new task. In our case this means taking the current time and printing it.

The andThen function is extremely important when using tasks because it lets us build complex chains.

One of the most common things you will want to do in a web app is talk to servers. The elm-http library provides everything you need for that, so let’s try to get a feel for how it works with the Http.getString function.

 Http.getString : String -> Task Http.Error String

We provide a URL, and it will create a task that tries to fetch the resource that lives at that location as a String. Looking at the type of the Task, finally that darn x is filled in with a real error type! This task will either fail with some Http.Error or succeed with a String.

## More Chaining

We have seen andThen used to chain two tasks together, but what if we want to chain lots of tasks? This can end up looking a bit odd, so you can bend the typical rules about indentation to make it look nicer. Let’s look at an example that chains a bunch of tasks together to measure how long it takes to evaluate the (fibonacci 20) expression:


import Time exposing (Time)

getDuration : Task x Time
getDuration =
getCurrTime
andThen \start -> succeed (fibonacci 20)
andThen \fib -> getCurrTime
andThen \end -> succeed (end - start)

fibonacci : Int -> Int
fibonacci n = if n <= 2 then 1 else fibonacci (n - 1) + fibonacci (n - 2)

port runner : Task x ()
port runner = getDuration andThen printIt

main = ..

printIt :


This reads fairly naturally. Get the current time, run the fibonacci function, get the current time again, and then succeed with the difference between the start and end time.

You might be wondering “why is start in scope two tasks later?” The trick here is that an anonymous function includes everything after the arrow. So if we were to put parentheses on our getDuration function, it would look like this:

getDuration : Task x Time
getDuration = getCurrentTime andThen
(\start -> succeed (fibonacci 20)
andThen (\fib -> getCurrTime andThen
(\end -> succeed (end - start))))

Now you can really see how weird our indentation is! The point is that you will see this chaining pattern relatively often because it lets you keep a bunch of variables in scope for many different tasks.

## Error Handling

So far we have only really considered tasks that succeed, but what happens when an HTTP request comes back with a 404 or some JSON cannot be decoded? There are two main ways to handle errors with tasks. The first is the onError function:

 onError : Task x a -> (x -> Task y a) -> Task y a

Notice that it looks very similar to andThen but it only gets activated when there is an error. So if we want to recover from a bad JSON request, we could write something like this:


import Http
import Json.Decode as Json
import Task exposing (Task, andThen, onError, succeed)

get : Task Http.Error (List String)
get = Http.get (Json.list Json.string) "http://example.com/hat-list.json"

safeGet : Task x (List String)
safeGet = get onError (\err -> succeed)

port runner : Task x ()
port runner = safeGet andThen printIt

main =


With the get task, we can potentially fail with an Http.Error but when we add recovery with onError we end up with the safeGet task which will always succeed. When a task always succeeds, it is not possible to pin down the error type. The type could be anything, we will never know because it will never happen. That is why you see the free type variable x in the type of safeGet.

The second approach to error handling is to use functions like Task.toMaybe and Task.toResult.

toMaybe : Task x a -> Task y (Maybe a)
toMaybe task = Task.map Just task onError \_ -> succeed Nothing

toResult : Task x a -> Task y (Result x a)
toResult task = Task.map Ok task onError \msg -> succeed (Err msg)


This is essentially promoting any errors to the success case.