Posted by LambdaCat on Tue, 04/19/2016 - 20:51

I've been developing a slightly bigger codebase than maybe average with Elm for a while, and I banged my head against some limits StartApp has. But an additional pattern has surfaced which helps a lot, even though it's not officially approved by Evan at the moment.

I'm going to show

Posted by Rundis on Thu, 04/07/2016 - 01:00

If you have worked with JavaScript (or quite a few other languages that embrace null) I bet you have had one or two errors that can be
traced back to an unexpected null reference. Some of them are obvious, but others are really tricky to
track down. I’m sure most of you are well aware that quite a few other languages banishes null and introduces a Maybe or Option type to handle nothingness.
Elm is one of those languages. Before I started looking at Elm I hadn’t really worked with Maybe types. In this blogpost
I thought I’d share a little more insight on how to work with them in Elm. I’ll also briefly cover how they might be (or not) used in JavaScript for reference.

Elm Maybe

Elm is a statically typed language which compiles down to JavaScript. Types is a core ingredient of Elm, that’s not the case with JavaScript obviously.

type Maybe a = Just a | Nothing

The Maybe type in Elm looks deceivingly simple. And actually it is.
The type is parameterized and the a is a placeholder for a concrete type in your program.
So a here means any type (Int, String, Float etc). A Maybe can have one of two values; either Just some value of type a or it is Nothing.
Where does Just and Nothing come from ? Are they defined somewhere else ? They are part of the type definition, think of them as tags. The name of these "tags"
must start with an upper case letter in Elm.

x = Just 0.0 -- Just 0.0 : Maybe.Maybe Float (1)

y = Nothing -- Nothing : Maybe.Maybe a (2)

1
The variable x Maybe with the tag Just and the Float value 0.0 (Maybe lives in a namespace or rather module in Elm called Maybe, that’s why the actual type definitions states Maybe.Maybe)

2
The variable y becomes a Maybe with the tag Nothing. Nothing has no value, and hence no value type associated. Nothing is Nothing, but it’s still a Maybe though :-)

Quick detour - Type annotations

Elm is a statically typed language, everything is represented through types. So before
we carry on I’d like to briefly cover the concept of type annotations.

Since JavaScript doesn’t have types, I’ll use Java as a comparable example

Sample Java functions

public int increment(int value) {
return value++;
}

public int add (int x, int y) {
return x + y;
}

Type annotated equivalents in Elm

increment : Int -> Int (1)
increment value =
value = value + 1

add : Int -> Int -> Int (2)
add x y =
x + y

1
The type annotation for increment tells us it is a function which takes an argument of type Int and returns an Int

2
add takes two arguments of type Int and returns a an Int. So think of the last one as return type.

Type annotations in Elm are optional, because the compiler is able to infer the types statically.
Most people tend to use type annotations because they provide very useful documentation.
When working with Elm it’s really something you quickly have to learn, because most documentation will use them
and the Elm compiler will most certainly expose you to them.

Getting the actual values from a Maybe

Ok so I have this maybe thing which can be a Just some value or Nothing. But how do I get
hold of the value so I can work with it ?

Pattern matching

myList : List String (1)
myList = ["First", "Second"] (2)

-- List.head : List a -> Maybe.Maybe a (3)

case List.head myList of (4)
Nothing -> (5)
"So you gave me an empty list!"

Just val -> (6)
val

-- returns "First"

1
Type annotation for myList. It is a List of String. It’s just a value, so that’s why there is no arrows in the type annotation

2
We are using a list literal to define our list. Each list item must be separated by a comma. It’s also worth noting, that every item in the list must be of the same type. You can’t mix Strings with Ints etc. The Elm compiler will yell at you if you try

3
I’ve added the type annotation for the List.head function. Given a List of values with type a it will return a Maybe of type a. List.head returns the first item of a List. The reason it returns a Maybe is because the List might be empty.

4
You can think of case as a switch statement on stereoids. Since List.head return a Maybe we have to possible case’s we need to handle

5
In this instance we can see from the code this case will never happen, we know myList contains items. The Elm compiler is really smart, but not that smart so it doesn’t know the list is empty.

6
This case unwraps the value in our Just so that we can use it. We just return the value, which would be "First".
The value is unwrapped using something called pattern matching. In JavaScript terms you might think of it as destructuring

The Maybe module
The Maybe type is defined in a module called Maybe. In addition to the Maybe type it also includes a collection
of handy functions that makes it handy to work with Maybe types in various scenarios.

myList = ["First", "Second", "Third"]

first = List.head myList
second = List.head (List.drop 1 myList)
tail = List.tail myList -- Just ["Second","Third"] : Maybe (List String)

-- Maybe.withDefault : a -> Maybe a -> a (1)
Maybe.withDefault "No val" first -- -> "First" (2)
Maybe.withDefault "No val" (List.head []) -- -> "No val"

-- Maybe.map : (a -> b) -> Maybe a -> Maybe b (3)
Maybe.map String.toUpper first -- -> Just "FIRST" (4)
Maybe.map String.toUpper Nothing -- -> Nothing

-- Maybe.map2 (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c (5)
Maybe.map2 (\a b -> a ++ ", " b) first second -- -> Just "First, Second" (6)
Maybe.map2 (\a b -> a ++ ", " b) first Nothing -- -> Nothing
Maybe.map2 (++) first second -- -> Just "First, Second" (7)

-- Maybe.andThen Maybe.Maybe a -> (a -> Maybe b) -> Maybe b (8)
Maybe.andThen tail List.head -- -> Just "Second" (9)
tail `Maybe.andThen` List.head -- -> Just "Second" (10)

tail
`Maybe.andThen` List.head
`Maybe.andThen` (\s -> Just (String.toUpper s)) -- -> Just "SECOND" (11)

Just []
`Maybe.andThen` List.head
`Maybe.andThen` (\s -> Just (String.toUpper s)) -- -> Nothing (12)

1
Maybe.withDefault takes a default value of type a a Maybe of type a. It returns the value of the maybe if it has a value (tagged Just) otherwise it returns the provided default value

2
In the first example first is Just "First" so it unwraps the value and returns that. In the second example there is no value so it returns the provided default

3
Maybe.map takes a function which has the signature (a → b), that means a function that takes any value of type a and return a value of type b (which can be the same type or a completely different type). The second argument is a Maybe (of type a). The return value is a Maybe of type b. So Maybe.map unwraps the second argument, applies the provided function and wraps the result of that in a Maybe which in turn is returned.

4
String.toUpper takes a String (a if you like) and returns a String (b if you like). String.toUpper doesn’t understand Maybe values, so to use it on a Maybe value we can use Maybe.map

5
Maybe.map2 is similar to Maybe.map but the function in the first argument takes two in parameters. In addition to the function param we provide two Maybe values. These two doesn’t need to be of the same type, but happens to be so in our example. There is also map3, map4 etc up to map8

6
If any or both of the two Maybe params are Nothing the result will be Nothing.

7
In the example above we used an anonymous function (lambda). However ++ is actually a function that takes two arguments so we can use that as the function argument

8
Maybe.andThen resembles Maybe.map but there are two vital differences. The function argument comes as the second param (we’ll come back to why), secondly the function in the function argument must return a Maybe rather than a plain value.

9
The first argument tail is a Maybe, the second argument is List.head which is a function that takes a list as an argument and returns a Maybe, so that conforms to the function params signature required by Maybe.andThen

10
In this version we use the infix version of andThen (marked by backticks before and after). This is the reason the function argument comes second, so you typically use Maybe.andThen when you you need to work with maybes in a pipeline sort of fashion.

11
This is an example of piping values when dealing with Maybe values. We start with the tail of our list and then we pick out the head of that list and then we convert the value of that to uppercase

12
You can almost think of andThen as a callback. If any step of the chain returns Nothing, the chain is terminated and Nothing is returned

Don’t like the way Maybe sound, how about rolling your own ?

type Perhaps a = Absolutely a | NotSoMuch

Of course interop with others will be an issue and Maybe has some advantages being part of the core library. But still
if you really really want to…​

JavaScript null/undefined

function headOfList(lst) {
if (lst && lst.length > 0) {
return lst[0];
} else {
// hm... not sure. let's try null
return null;
}
}

function tailOfList(lst) {
if (lst && lst.length > 1) then
return lst.slice(0);
} else {
// hm... not sure. let's try null
return null;
}
}

var myList = ["First", "Second", "Third"];
var first = headOfList(myList); // "First"
var second = headOfList(tailOfLIst(myList)) // "Second"
var tail = tailOfList(lst); // ["First", "Second"]

first // "First"

headOfList([]) // null (1)

first.toUpperCase() // "FIRST"
headOfList([]).toUpperCase() // Type Error: Cannot read property 'toUpperCase' of null (2)

first + ", " + second // "First, Second"
first + ", " + null // "First, null" (3)

headOfList(tail).toUpperCase() // "SECOND"
headOfList([]).toUpperCase() // Type Error: Cannot read property 'toUpperCase' of null (4)

1
An empty list obviously doesn’t have a first item.

2
If this was in a function you might guard against this. But what would you return ? Would you throw a exception ?

3
Doesn’t look to cool, so you would have to make sure you guarded against this case. Let’s hope you tested that code path, otherwise it’s lurking there waiting to happen !

4
Same as 2

Okay so most of this cases are pretty silly, we would have to come up with something more real life
with functions calling functions calling functions etc. The bottom line is that you have to deal with it,
but it’s up to you all the time to make sure nulls or undefined doesn’t sneak in. In most cases there are simple non verbose
solutions to deal with them, but it’s also quite easy to miss handling them. If you do it can sometimes be quite a challenge tracking down
the root cause.

It’s undoubtably a little more ceremony in Elm, but in return you will not ever get nullpointer exceptions.

Introducing Maybe in JavaScript

If you are from a JavaScript background the blogpost Monads in JavaScript gives you a little hint on how you could implement Maybe in JavaScript.

Let’s borrow some code from there and see how some of the examples above might end up looking

Defining Just and Nothing

function Just(value) {
this.value = value;
}

Just.prototype.bind = function(transform) {
return transform(this.value);
};

Just.prototype.map = function(transform) {
return new Just(transform(this.value));
};

Just.prototype.toString = function() {
return 'Just(' + this.value + ')';
};

var Nothing = {
bind: function() {
return this;
},
map: function() {
return this;
},
toString: function() {
return 'Nothing';
}
};

A few helper functions for dealing with JavaScript arrays

function listHead(lst) {
return lst && list.length > 0 ? new Just(lst[0]) : Nothing;
}

function listTail() {
return lst && list.length > 1 ? new Just(lst.slice[1]) : Nothing;
}

Elm examples in JavaScript with Maybe’ish support

var myList = ["First", "Second", "Third"];
var first = listHead(myList);
var second = listTail(myList).bind(t => listHead(t));
var tail = listTail(myList);

// Similar to Maybe.map in Elm
first.map(a => a.toUpperCase()) // Just {value: "FIRST"} (1)
Nothing.map(a => a.toUpperCase()) // Nothing (object) (2)

// Similar to Maybe.map2 in Elm
first.bind(a => second.map( b => a + ", " + b)) // Just { value: 'First, Second' } (3)
first.bind(a => Nothing.map( b => a + ", " + b)) // Nothing (object)

// Similar to Maybe.andThen in Elm
tail.bind(a => listHead(a)).bind(b => new Just(b.toUpperCase())) // Just { value: 'SECOND' } (4)
new Just([]).bind(a => listHead(a)).bind(b => new Just(b.toUpperCase())) // Nothing (object) (5)

1
first is a Just object. Since it has a value the arrow function is run as expected

2
When the value is Nothing (a Nothing object) toUpperCase is never run and the Nothing object is returned

3
In the arrow function of bind for first we ignore the unwrapped value and call map on second with a new arrow function which now has both the unwrapped value of both a and b. We concatenate the values and the map function ensures the result is wrapped up in a Just object
If you remember the elm case for map2, that was a separate function. Here map is just a convenience to wrap up the innermost value in a Just.

4
tail is a Just object with the value ["First", "Second"] in the first level arrow function we pick out the head which returns a Just object with the value "Second". In the innermost arrow level function we do upperCase on the value and wrap in it a Just which is the end result.

5
We are starting with Just with a value of an empty array. In the first level arrow function we try to pick out the head of the list. Since that will return a Nothing object, Nothing passes straight through the second level arrow function, never executing the toUpperCase call.

So as you can see it is possible to introduce the notion of Maybe in JavaScript. There are several libraries out there to choose from
I haven’t really tried any of them. Regardless the issue you’ll be facing is that the other libraries you are using probably won’t be using your representation of Maybe if at all.
But hey, maybe it’s better with something than nothing. Or whatever.

Wrapping up

There is clearly a slight cost with explicitly handling nothingness everywhere. In Elm you basically don’t even have a choice. The type system
and the compiler will force you into being explcit about cases when you don’t have a value. You can achieve the same as with null but
you always have to handle them. In your entire program. The most obvious benefit you get, is that you simply will not get null reference related errors in Elm. When calling any function
that accepts Maybe values as input params or return Maybe values you will be made well aware of that. The compiler will let you know, but typically you would also see type annotations stating this fact too.
This explicitness is actually quite liberating once you get used to it.

In JavaScript you can try to be more explicit with nulls. You can even reduce the chances of null pointers ever happening by
introducing a Maybe/Option like concept. Of course you wouldn’t introduce the possibility of null pointers in your code. However there’s a pretty big chance
some bozo,responsible for one of the 59 libs you somehow ended up with from npm, have though.

There are plenty of bigger challenges than null pointer exceptions out there, but if you could avoid them altogether,
surely that must a be of some benefit. I’ll round off with the obligatory quote from Tony Hoare as you do when one pays tribute to our belowed null.

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

— Tony Hoare

Posted by Rundis on Mon, 03/28/2016 - 01:00

Maybe you are a package author for Elm packages you wish to publish to http://package.elm-lang.org/
. Or maybe you are thinking about authoring a package. Before you publish something to the package repo
you have to write documentation for your package. Wouldn’t it be sweet if you could preview the
generated documentation from the comfort of your editor ?

The good news is that with the latest (0.3.6) edition of the elm-light plugin
you can !

Demo

Link to demo

Feature highlights

  • Preview how the docs will look for each individual module
  • The preview is updated whenever you save your (exposed) Elm module file
  • Layout pretty close to how it will look on http://package.elm-lang.org/ once published
  • Fast (at least on my machine !)
  • Minor detail, but the entire preview UI is also implemented in Elm (ultimate dogfooding). It’s
    basically a modified and simplified implementation of the package preview code for http://package.elm-lang.org/

Resources

Posted by Gizra on Thu, 03/24/2016 - 00:00

The 2nd Elm - TLV meetup was lots of fun. But it was in Hebrew, and the intersection
between Hebrew speakers and Elm devs is still very limited.

So I've re-recorded my presentation in English. If you haven't ever seen Elm, or
already develop in Elm and want to better understand "how to think Elm" this presentation
might give you a nudge in the right direction.

Continue reading…

Posted by Gizra on Thu, 03/24/2016 - 00:00

We've reached the point we needed to translate one of our Elm apps to multiple languages.

View demo
Get the source code

We looked for existing solutions and stumbled upon the elm-i18n library. This
of course made us happy, to see that someone has already solved the problem for us. However, when we looked at the example something felt missing. Type safety.

In Gizra we deal most hours of our working days with PHP, so you can say we are
fearless developers. That is, we hope and believe our code is right, and
worst case we know we will catch our bugs on run time.

But Elm can make us better developers, and give us some safety!

Continue reading…

Posted by codecentric on Mon, 03/21/2016 - 11:30

Elm Friday: Union Types
In the last episode we took a look at some of the type constructs Elm provides, namely type aliases and records. We continue in this episode by looking at the last major type construct, union types.

About This Series
This is the tenth post in a series of short and sweet blog posts about Elm. The stated goal of this series is to take you from “completely clueless about Elm” to “chief Elm guru”, step by step. If you have missed the previous episodes, you might want to check out the table of contents.
Union Types
Union types are similar to enumerations, which you might now from other languages. A simple union type could look like this:

type Fruit = Apple | Banana | Orange

This declares a new union type that has three possible values (Apple, Orange and Banana in this example).
Union types and case statements are a perfect match, so this is a good time to introduce the case statement:

module Main (..) where

import Html exposing (..)

type Fruit
= Apple
| Banana
| Orange

fruitToProverb : Fruit -> String
fruitToProverb fruit =
case fruit of
Apple -> "An apple a day keeps the doctor away"
Banana -> "Time flies like an arrow; fruit flies like a banana."
Orange -> "An orange never bears a lime."

main : Html
main =
ul
[]
[ li [] [ Apple |> fruitToProverb |> text ]
, li [] [ Banana |> fruitToProverb |> text ]
, li [] [ Orange |> fruitToProverb |> text ]
]

The case-of statement in the fruitToProverb function takes an expression (in this case the fruit parameter) and matches it against all listed cases. The branch that matches will be used. So, if you pass the value Apple into the function it will return the string "An apple a day keeps the doctor away".
Tagged Unions
Union Types can actually be more than simple enumerations – they can carry additional data. This is achieved with tagged unions. The following example defines a union type Shape with two possible shapes:

-- A record definition. We had a look at records in the previous episode.
type alias Point =
{ x : Float
, y : Float
}

-- the tagged union Shape
type Shape
= Circle Point Float
| Rectangle Point Point

Each kind of Shape defines different data that can be attached to it. A Circle can hold a Point (its center) and its radius (represented as a Float). The Rectangle in contrast has two Points attached to it, representing the upper left and the lower right corner. Keep in mind that the number of parameters and their types can be different for each member of a tagged union. You can also mix tagged union types with simple values (that do not have additional data).
Here is an example where you can see a tagged union in action:

import Html exposing (..)

type alias Point =
{ x : Float
, y : Float
}

type Shape
= Circle Point Float
| Rectangle Point Point

area : Shape -> Float
area shape =
case shape of
Circle center radius ->
pi * radius ^ 2

Rectangle corner1 corner2 ->
abs (corner1.x - corner2.x) * abs (corner1.y - corner2.y)

main : Html
main =
let
circle =
Circle { x = 2.3, y = 1.4 } 3.1

rectangle =
Rectangle { x = 0.5, y = 1.4 } { x = 3.5, y = 5.2 }
in
ul
[]
[ li [] [ area circle |> toString |> text ]
, li [] [ area rectangle |> toString |> text ]
]

The interesting part is the area function. We use a case statement to calculate the area of a geometrical shape, which requires a different formula depending on the kind of shape. We also use the case statement to destructure the tagged union values, that is, to assign an identifier to the values inside the tagged union members. For example, the line
Circle center radius ->
enables us to access the Float attached to the Circle and use it in our calculation by assigning the identifier radius to it. Since we do not use the center value at all (it is not relevant for calculating the area), we also use the underscore wildcard (_) which basically translates to "ignore this value, we do not need it here":
Circle _ radius ->
That’s about it on union types and tagged unions, so this concludes the tenth episode of this blog post series on Elm. Stay tuned for the next episode!
The post Elm Friday: Type System Basics – Union Types and Tagged Unions (Part X) appeared first on codecentric Blog.

Posted by Rundis on Mon, 03/14/2016 - 01:00

The elm-light plugin provides a pretty useful
featureset for developing elm applications. Until now all features have been implemented
using a combination of ClojureScript and JavaScript. But wouldn’t it be cool if the plugin
implemented Elm features using Elm where that’s feasible ? Elm compiles to JavaScript and
JavaScript interop in ClojureScript is quite easy so it shouldn’t be that hard really.

If nothing else I thought it would be a fun challenge, so I set forth and decided to implemented
a simple module browser for Elm projects.

Elm for the UI

In Elm it’s recommended that you follow The Elm Architecture (AKA: TEA).
You model your Elm application and components into 3 separate parts; Model, View and Update.
The easiest way to get started with implementing something following TEA is using the start-app package.

Model

Quite often you’ll find that you start by thinking about how to design your model.
This was also the case for me when developing the module browser.

type alias Model = (1)
{ allModules : List Modul
, filteredModules : List Modul
, searchStr : String
, selected : Maybe Modul
}

type alias Modul = (2)
{ name : String
, file : String
, packageName : String
, version : String
}

1
The model is quite simple and contains; a list of all modules, the currently filtered modules, the search string entered by the user and the currently selected module

2
Since Module is a reserved word in Elm the type used for representing a project Module is doofily named Modul.

For more info about what Elm modules are check out the elm-guides

UPDATE

Update is where we actually implement the logic of our Elm application. I won’t cover
all the details, but let’s walk through the most important bits.

type Action (1)
= NoOp
| Filter String
| Prev
| Next
| Select
| ClickSelect String
| Close
| Refresh (List Modul)

update : Action -> Model -> ( Model, Effects Action ) (2)
update action model =
case action of
NoOp -> (3)
( model, Effects.none )

Filter str -> (4)
let
filtered =
filterModules str model.allModules

sel =
List.head filtered
in
( { model
| searchStr = str
, filteredModules = filtered
, selected = sel
}
, Effects.none
)

Prev -> (5)
( { model | selected = prevModule model }
, notifyChangeSelection
)

Next ->
( { model | selected = nextModule model }
, notifyChangeSelection
)

Select -> (6)
case model.selected of
Nothing ->
( model, Effects.none )

Just x ->
( model
, notifySelect x.file
)

ClickSelect file -> (7)
( model
, notifySelect file
)

Close -> (8)
( model, notifyClose )

Refresh modules -> (9)
( Model modules modules "" (List.head modules)
, Effects.none
)

1
The actions that causes changes to the model is represented by a Union Type called Action.
If you’re not sure what union type means, think of it as a Enum on stereoids.

2
The update function takes an action and the current model as parameters and returns
a tuple of an (possibly) updated model and an Effect. Effects are basically things that have side-effects (http/ajax, interacting with the browser etc).
We treat an effect like a value in the application, the Elm runtime takes care of actually executing it.

3
NoOp is just that. It’s handy when initializing the app and also for mapping effects to when there are
effects that we don’t care about in the context of this update function

4
Whenever the user changes the search string input the Filter action is called. It uses a filterModules helper function
to filter modules with names starting with the given search string. We default the selected
module to the first in the filtered results. The model is NOT mutated, rather we return a new updated model.
Elm keeps track of our global model state !

5
Prev and Next selects/highlights the next/previous module given the currently selected one.
The notifyChangeSelection function call results in an effect that allows us to communicate with the ClojureScript part
of the module browser feature. We’ll get back to that further on.

6
The Select action is triggered when the users presses Enter. It selects the module and should
ultimately result in opening the Elm Module file. Again to make that happen we need to communicate
with our ClojureScript backend. This is achived through the notifySelect helper function.

7
ClickSelect is similar to Select but handles when the user uses the mouse to select a module.

8
Close - When the user presses the escape key, the module browser should close. Again we
need to notify the ClojureScript backend

9
To populate the Module browser ui with modules the Refresh action is called. This action
is actually triggered by our ClojureScript backend.

Before we dive into more details about the interop with ClojureScript, let’s quickly go through
the view rendering logic.

VIEW

The view part in Elm is also entirely functional and you as an application developer
never touches the DOM directly. Given the current Model you tell Elm what the view should look
like, and Elm (through the use of Virtual DOM) takes care of efficiently
updating the DOM for you.

The view for the module browser is really quite simple and consist of a search input field
and an ul for listing the modules.

modulebrowser

view : Signal.Address Action -> Model -> Html (1)
view address model =
div
[ class "filter-list" ] (2)
[ searchInputView address model
, ul
[]
(List.map (\m -> itemView address m model) model.filteredModules) (3)
]

searchInputView : Signal.Address Action -> Model -> Html (4)
searchInputView address model =
let
options =
{ preventDefault = True, stopPropagation = False }

keyActions =
Dict.fromList [ ( 38, Prev ), ( 40, Next ), ( 13, Select ), ( 27, Close ) ] (5)

dec =
(Json.customDecoder (6)
keyCode
(\k ->
if Dict.member k keyActions then
Ok k
else
Err "not handling that key"
)
)

handleKeydown k = (7)
Maybe.withDefault NoOp (Dict.get k keyActions) |> Signal.message address
in
input (8)
[ value model.searchStr
, class "search"
, type' "text"
, placeholder "search"
, on "input" targetValue (\str -> Signal.message address (Filter str))
, onWithOptions "keydown" options dec handleKeydown
]
[]

itemView : Signal.Address Action -> Modul -> Model -> Html
itemView address mod model = (9)
let
pipeM = (10)
flip Maybe.andThen

itemClass = (11)
model.selected
|> pipeM
(\sel ->
if (sel == mod) then
Just "selected"
else
Nothing
)
|> Maybe.withDefault ""
in
li
[ class itemClass
, onClick address (ClickSelect mod.file)
]
[ p [] [ text mod.name ]
, p [ class "binding" ] [ text (mod.packageName ++ " - " ++ mod.version) ]
]

1
The main view function takes an Address and the current Model as input and returns
a virtual HTML that represents the UI we want rendered. In Elm we use something called mailboxes
to respond to user interactions. Check out the note section below for more details if you’re interested.
In short the address param is the address to a given mailbox. Elm picks up any messages in the mailbox, handles them
and ultimately the results flow back to our application through the previously described update function.

2
All HTML tags have a corresponding function and all follow the same pattern. The first argument is a list of attributes,
the second is a list of sub elements.

3
The beauty of everything being a function (as opposed to templating languages) is that you have the full power of the language
to construct your view. Map, filter, reduce etc to your heart’s content.

4
The searchInputView function renders the search input field. This is where most of the user interaction stuff happens
so it’s naturally the most complex part of the UI.

5
We use the Dict type to represent key/values. Think map if you’re from a Clojure background! The keyActions
map lists the keycode and update action combo we are interested in handling.

6
We want to intercept just the given keyCodes everything else should flow through and update the searchStr in our model.
To support that we need to implement a custom decoder for the keydown event.

7
You can read handleKeydown as follows, if the keyCode lookup for the given k returns an Action use that
otherwise use the default NoOp action. The result from that is used as the last param of the Signal.message function.
(In Clojure terms you can think of |> as thread-last). Signal.message sends the given action to the given address.

8
The search input handles changes to the input by triggering the Filter action with a payload
which is the current value of the input. To handle the special characters we handle the keydown event using
the local helper function we outlined in <7>.

9
itemView constructs the view for each individual item. Most of the logic here is related to giving the
currently selected item it’s own css class.

10
Maybe.andThen is a function to help you chain maybes.
(There is no such thing as null/nil in Elm !). flip flips the order of the two first arguments, and we do it to allow us to chain calls using the |> operator

11
If an item is selected and the selected item is the same as the current module being rendered then the class should be selected in all other cases
the class is an empty string.

To understand more about Mailboxes, Addresses and the term Signal in Elm. You might want
to check out the relevant Elm docs or maybe this nice blog post

Interop with ClojureScript using Ports

Interop with JavaScript in Elm goes through strict boundaries and use a mechanism called ports.
The strict boundary is in place to ensure that you can’t get runtime exceptions in Elm (due to nulls, undefined is not a function, type mismatches etc etc).
At first it feels a little bit cumbersome, but really the guarantees given from Elm makes up for it in the long run. Big time.

The following blog post really helped me out when doing the ports stuff; "Ports in Elm"

-- Inbound

modzSignal : Signal Action (1)
modzSignal =
Signal.map Refresh modzPort

port modzPort : Signal (List Modul) (2)

-- Outbound

selectMailbox : Signal.Mailbox String (3)
selectMailbox =
Signal.mailbox ""

port select : Signal String (4)
port select =
selectMailbox.signal

changeSelectionMailbox : Signal.Mailbox () (5)
changeSelectionMailbox =
Signal.mailbox ()

port changeSelection : Signal () (6)
port changeSelection =
changeSelectionMailbox.signal

closeMailbox : Signal.Mailbox ()
closeMailbox =
Signal.mailbox ()

port close : Signal ()
port close =
closeMailbox.signal

1
Signals are basically values that changes over time. A signal always has a value.
If you remember our update function, it takes an Action as the first argument. To allow
our incoming module list to trigger an update we need to convert the value we receive from the
modzPort to a Refresh action (with a payload which is a List of Modul records)

2
modzPort is a port which is a Signal that receives values from outside of Elm. Typically JavaScript
or in our instance ClojureScript. A Signal always has a value, so you will see that we need to provide an initial value
when we start the elm app from ClojureScript later on.

3
When using the Elm start app package we typically use mailboxes to
achieve (side-) effects. So to send messages to JavaScript (or ClojureScript!) we create an intermediary mailbox
to communicate through an outgoing port. When we select a module in the module browser we send the file name of the module
we wish to open and the type of the file name is String. Hence the Mailbox is a mailbox for string messages.

4
The select port is a Signal of Strings (file names) that we can subscribe to from JavaScript(/ClojureScript).
You can think of it as an Observable (in RxJs terms) or maybe simpler an event emitter if you like.

5
() in Elm means the same as void or no value.

6
When the user changes which module is selected/hightlighted we don’t care about the value, in this instance we just need to know that the user changed their selection

Wiring up Elm with Start app

app : StartApp.App Model (1)
app =
StartApp.start
{ init = init
, update = update
, view = view
, inputs = [ modzSignal ] (2)
}

main : Signal Html (3)
main =
app.html

port tasks : Signal (Task.Task Never ()) (4)
port tasks =
app.tasks

1
StartApp.start takes care of wiring up our Elm application. init creates an initial empty Model, the other functions
we have already described.

2
StartApp also takes an inputs argument, here we need to remember to add our modzSignal so that it
is picked up and handled by StartApp.

3
main is the entry point for any Elm application.

4
Elm executes side effects through something called tasks
I won’t go into details here, but just remember to add this incantation when using StartApp.

Wrapping up the Elm part

Right so that wes pretty much all there is to the Elm part. Of course we also need to remember to compile
the Elm code to JavaScript before we can use it from Light Table.
To do that we use the elm-make executable that comes with the elm-platform installation

I can assure you that I didn’t get a single run time exception whilst developing the Elm part. It did
get lots of helpful compiler errors along the way, but as soon as the compiler was happy the Elm application ran just as expected.
It’s hard to describe the experience, but trust me, it’s certainly worth a try !
To be able to easily test and get visual feedback along the way I set up a dummy html page.

Ok let’s move on to the ClojureScript part were we hook the ui up to the Light Table plugin.

ClojureScript and Light Table

Generating the list of Elm Modules

Unfortunately there isn’t any API AFAIK that provides the information I wished to present
(ideally all modules and for each module, all it’s publicly exposed functions/types/values).
So I had to go down a route where I use a combination of the elm project file (elm-package.json) and
artifacts (files) generated when you run elm-make on your elm project.

(defn- resolve-module-file [project-path pck-json package module version] (1)
(->> pck-json
:source-directories
(map #(files/join project-path
"elm-stuff/packages"
package
version
%
(str (s/replace module "." files/separator) ".elm")))
(some #(if (files/exists? %) % nil))))

(defn- get-exposed-modules [project-path {:keys [package exact]}] (2)
(let [pck-json (u/parse-json-file (files/join project-path
"elm-stuff/packages"
package exact
"elm-package.json"))]
(->> pck-json
:exposed-modules
(map (fn [x]
{:name x
:packageName package
:version exact
:file (resolve-module-file project-path pck-json package x exact)})))))

(defn- get-package-modules [project-path] (3)
(->> (u/get-project-deps project-path)
(filter :exact)
(mapcat (partial get-exposed-modules project-path))
(sort-by :name)))

(defn- deduce-module-name [root-path elm-file-path] (4)
(-> elm-file-path
(s/replace root-path "")
(s/replace ".elm" "")
(s/replace #"^/" "")
(s/replace files/separator ".")))

(defn- get-project-modules [project-path] (5)
(let [pck-json (u/parse-json-file (files/join project-path "elm-package.json"))]
(->> (:source-directories pck-json)
(mapcat (fn [dir]
(if (= dir ".")
(->> (files/ls project-path) ;; fixme: no nesting allowed to avoid elm-stuff etc
(filter #(= (files/ext %) "elm"))
(map (fn [x]
{:name (deduce-module-name "" x)
:file (files/join project-path x)})))
(->> (files/filter-walk #(= (files/ext %) "elm") (files/join project-path dir))
(map (fn [x]
{:name (deduce-module-name (files/join project-path dir) x)
:file x}))))))
(map (fn [m]
(assoc m :packageName (files/basename project-path) :version (:version pck-json))))
(sort-by :name))))

(defn get-all-modules [project-path] (6)
(concat
(get-project-modules project-path)
(get-package-modules project-path)))

1
Helper function which tries to resolve the file for a Module from a 3rd party library

2
Every 3rd party library also comes with a elm-package.json that lists which module are
publicly exposed. This helper function generates module info for all exposed modules from a 3rd party library

3
Given all defined project dependencies for a project at a given project-path this function generates
module informaation for all this packages. It will only try to resolve modules which has a resolved version :exact, so there is a precondition
that you have run either elm-package install or elm-make successfully on your project first.

4
deduce-module-name is a helper function which tries to deduce the module name for an Elm file in your project

5
Helper function that takes a simplistic approach to try to find all modules in you project and generate module information for them
It uses the "source-directories" key in your project’s elm-package.json as a starting point.

6
The complete list of modules is a concatination of 3rd party modules and your project modules.

There are a few simplifications in this implementation that might yield incomplete results (and sometimes erronous).
However for the majority of cases it should work fine.

Light Table sidebar

The module browser will live in the right sidebar in Light Table. The following code will
construct the wrapper view and a Light Table object that will allow us to wire up the appropriate
behaviors.

(defui wrapper [this] (1)
[:div {:id "elm-module-browser"} "Retrieving modules..."])

(object/object* ::modulebrowser (2)
:tags #{:elm.modulebrowser}
:label "Elm module browser"
:order 2
:init (fn [this]
(wrapper this)))

(def module-bar (object/create ::modulebrowser)) (3)

(sidebar/add-item sidebar/rightbar module-bar) (4)

1
Helper function to create a wrapper div which will host our module browser

2
A Light Table object (basically an ClojureScript atom) that allows us to tag behaviors.

3
The object above is instantiated at start up

4
We add the module bar to the right hand sidebar in Light Table

Light Table behaviors

(behavior ::clear! (1)
:triggers #{:clear!}
:reaction (fn [this]
(cmd/exec! :close-sidebar)))

(behavior ::focus! (2)
:triggers #{:focus!}
:reaction (fn [this]
(let [input (dom/$ "#elm-module-browser input")]
(.focus input))))

(behavior ::ensure-visible (3)
:triggers #{:ensure-visible}
:reaction (fn [this]
(sidebar-cmd/ensure-visible this)))

(behavior ::show-project-modules (4)
:triggers #{:show-project-modules}
:reaction (fn [this prj-path]
(let [modules (get-all-modules prj-path)
el (dom/$ "#elm-module-browser")
mod-browser (.embed js/Elm js/Elm.ModuleBrowser el (clj->js {:modzPort []}))] (5)

(.send (.-modzPort (.-ports mod-browser)) (clj->js modules)) (6)

;; set up port subscriptions

(.subscribe (.-changeSelection (.-ports mod-browser)) (7)
(fn []
(object/raise this :ensure-visible)))

(.subscribe (.-select (.-ports mod-browser))
(fn [file]
(cmd/exec! :open-path file)
(object/raise this :clear!)))

(.subscribe (.-close (.-ports mod-browser))
(fn []
(object/raise this :clear!)))

(object/raise this :focus!))))

(behavior ::list-modules (8)
:triggers #{:editor.elm.list-modules}
:reaction (fn [ed]
(when-let [prj-path (u/project-path (-> @ed :info :path))]
(do
(object/raise sidebar/rightbar :toggle module-bar)
(object/raise module-bar :show-project-modules prj-path)))))

(cmd/command {:command :show-modulebrowser (9)
:desc "Elm: Show module-browser"
:exec (fn []
(when-let [ed (pool/last-active)]
(object/raise ed :editor.elm.list-modules)))})

1
This behavior basically closes the module browser sidebar when triggered

2
We need to be able to set focus to the search input field when we open the module browser

3
Helper behavior that ensures that the currently selected item in the module browser is visible
on the screen. Ie it will scroll the div contents accordingly using a LT core helper function.

4
This is were we hook everything up. We gather the module information for the given project
instantiate the Elm app, subscribe to outgoing messages(/signals!) and populate the module browser
with the module list.

5
We start the elm app here and tells it to render in the wrapper div defined previously. We provide
an initial value for the modzPort with an empty list. (Could have provided the gathered list modules here, but wanted to show how you send messages to a inbound Elm port explicitly. See next step)

6
To populate the module browser we send a message to the modzPort. Elm port thinks in JavaScript so we need to convert our list of ClojureScript maps to a list of JavaScript objects

7
To listen to events from the Elm app we call subscribe with a given callback function. In this example
we trigger the ensure-visible behavior when the users moves the selection up or down, to ensure the selected item stays visible.

8
The behaviors above was tied(tagged) to the module-bar object, however this behavior is tagged to
a currently opened and active elm editor object. Light Table has no concept of projects, so to deduce which project we should
open the module browser for we need a starting point. Any elm file in your project will do. Based on that
we can deduce the root project path. If we find a project we display the module bar view and trigger the behavior
for populating the module browser.

9
Commands are the user interfacing functions that responds to user actions. They can be listed in the command bar in Light Table
and you can assign shortcuts to them. The show-modulebrowser command triggers the list-modules behavior.
Commands are available regardless of which editor you trigger them from, this is why we introduced the intermediary 'list-modules` behavior
because that allows us to declaritly filter when this behavior will be triggered. You’ll see how when we describe behaviors wiring in Light Table.

Wiring up LT behaviors

In our plugin behaviors file we need to wire up our behaviors.

[:editor.elm :lt.plugins.elm-light.modulebrowser/list-modules] (1)
[:elm.modulebrowser :lt.plugins.elm-light.modulebrowser/clear!] (2)
[:elm.modulebrowser :lt.plugins.elm-light.modulebrowser/show-project-modules]
[:elm.modulebrowser :lt.plugins.elm-light.modulebrowser/focus!]
[:elm.modulebrowser :lt.plugins.elm-light.modulebrowser/ensure-visible]

1
Here we tell Light Table that only editor objects with the tag :editor.elm
will respond with the list-modules behavior we described earlier

2
Similaritly the other behaviors will only be triggerd by objects tagged with :elm-modulebrowser.
In our case that would be the module-bar object we defined.

Why all this ceremony with behaviors ?
Flexibility! It allows us to easily turn on/off features while Light Table is running. If you wish you could quite easily
create your own implementation for a behavior and replace the one supplied by the plugin.
Or maybe you’d like to do something in addition for a given behavior trigger.

Conclusion

Okay let’s be honest. We haven’t set the world alight with a killer feature that couldn’t
be accomplished quite easily without Elm. Neither have we created an advanced demo for Elm and ClojureScript integration.
But we’ve certainly proven that it’s possible and it wasn’t particularily difficult.
It somehow feels better with an Elm plugin that has Elm as part of it’s implementation.

You can do some pretty awesomly advanced UI’s with Elm and combing it with ClojureScript is definitely feasible.
I’ll leave it to you to evaluate if that would ever make sense to do though !

Posted by Rundis on Tue, 03/01/2016 - 01:00

So the hypothesis from episode 3 was that it should
be relatively easy to add new features. In this episode we’ll put that hypothesis to the test and add CRUD features
for Albums. There will be a little refactoring, no testing, premature optimizations and plenty of "let the friendly Elm and Haskell compilers guide us along the way".

Useful resources

  • Check out the other episodes in this blog series.
  • The accompanying Albums sample app is on github, and there is a tag
    for each episode

Table of Contents

Introduction

When I set out to implement the features for this episode I didn’t really reflect on how I would then later
go about blogging about it. It turns out I probably did way to many changes to fit nicely into a blog episode.
Let’s just say I got caught up in a coding frenzy, but let me assure you I had a blast coding for this episode !
This means I wont be going into detail about every change I’ve made since the last episode, but rather try to highlight
the most important/interesting ones.

A highlevel summary of changes includes:

  • Haskell stack has been introduced to the backend
  • Implemented REST endpoints for Albums CRUD
    • Backend now composes endpoints for Artists and Albums
    • Data model changed to account for Album and Track entities
    • Bootstrapping of sample data extended and refactored to a separate module
  • Implemented UI for listing, deleting, creating, updating and displaying album details
    • In particular the the features for creating/updating Albums and associated tracks, gives a glimpse
      of the compasability powers of the Elm Architecture

Backend

Stack

Working with Cabal and Cabal sandboxes is a bit of a pain. Stack promises to alleviate some of those pains, so I figured
I’d give it a go. There are probably tutorials/blog posts out there going into how you should go about migrating
to use stack in your Haskell projects, so I won’t go into any details here.
Basically I installed stack and added a stack configuration file stack.yml. After that I was pretty much up and running.
The instructions for running the sample app with stack can be found in the Albums README.

Datamodel

albums db part4

The datamodel contains a little bit of flexibility so that a track can be potentially be included in many albums
(hence the album_track entity). For this episode though, we’re not using that and of course that innocent bit of flexibility
comes with a cost of added complexity. I considered removing the album_track entity, but decided against it. I figured
that in a real project this is a typical example of things you have to deal with (say you have a DBA or even more relevant…​ and exisiting datamodel you have to live with).
Let’s run with it, and try to deal with it along the way.

Bootstrapping

The code for schema creation and bootstrapping test data has been moved to a separate module.

backend/src/Bootstrap.hs

bootstrapDB :: Sql.Connection -> IO ()
bootstrapDB conn = do
createSchema conn
populateSampleData conn

createSchema :: Sql.Connection -> IO ()
createSchema conn = do
executeDB "PRAGMA foreign_keys = ON"
executeDB "create table artist (id integer primary key asc, name varchar2(255))"
executeDB "create table track (id integer primary key asc, name varchar2(255), duration integer)"
executeDB "create table album (id integer primary key asc, artist_id integer, name varchar2(255), FOREIGN KEY(artist_id) references artist(id))"
executeDB "create table album_track (track_no integer, album_id, track_id, primary key(track_no, album_id, track_id), foreign key(album_id) references album(id), foreign key(track_id) references track(id))"

where
executeDB = Sql.execute_ conn

-- definition of sample data omitted for brevity

populateSampleData :: Sql.Connection -> IO ()
populateSampleData conn = do
mapM_ insertArtist artists
mapM_ insertTrack tracks
mapM_ insertAlbum albums
mapM_ insertAlbumTrack albumTracks

where
insertArtist a = Sql.execute conn "insert into artist (id, name) values (?, ?)" a
insertTrack t = Sql.execute conn "insert into track (id, name, duration) values (?, ?, ?)" t
insertAlbum a = Sql.execute conn "insert into album (id, artist_id, name) values (?, ?, ?)" a
insertAlbumTrack at = Sql.execute conn "insert into album_track (track_no, album_id, track_id) values (?, ?, ?)" at

Somewhat amusing that foreign key constraints are not turned on by default in SQLite, but hey.
What’s less amusing is that foreign key exceptions are very unspecific about which contraints are violated (:

New endpoints for Albums

Model additions

backend/src/Model.hs

data Track = Track (1)
{ trackId :: Maybe Int
, trackName :: String
, trackDuration :: Int -- seconds
} deriving (Eq, Show, Generic)

data Album = Album (2)
{ albumId :: Maybe Int
, albumName :: String
, albumArtistId :: Int
, albumTracks :: [Track]
} deriving (Eq, Show, Generic)

1
Our Track type doesn’t care about the distiction between the album and album_track entities

2
It was tempting to add Artist as a property to the Album type, but opted for just the id of an Artist entity.
I didn’t want to be forced to return a full artist instance for every Album returned. You gotta draw the line somewhere right ?

Albums CRUD functions

In order to keep this blog post from becoming to extensive we’ve only included the functions to
list and create new albums. You can view the update, findById and delete functions in the
album sample repo

findAlbums :: Sql.Connection -> IO [M.Album] (1)
findAlbums conn = do
rows <- Sql.query_ conn (albumsQuery "") :: IO [(Int, String, Int, Int, String, Int)]
return $ Map.elems $ foldl groupAlbum Map.empty rows

findAlbumsByArtist :: Sql.Connection -> Int -> IO [M.Album] (2)
findAlbumsByArtist conn artistId = do
rows <- Sql.query conn (albumsQuery " where artist_id = ?") (Sql.Only artistId) :: IO [(Int, String, Int, Int, String, Int)]
return $ Map.elems $ foldl groupAlbum Map.empty rows

albumsQuery :: String -> SqlTypes.Query (3)
albumsQuery whereClause =
SqlTypes.Query $ Txt.pack $
"select a.id, a.name, a.artist_id, t.id, t.name, t.duration \
\ from album a inner join album_track at on a.id = at.album_id \
\ inner join track t on at.track_id = t.id"
++ whereClause
++ " order by a.id, at.track_no"

groupAlbum :: Map.Map Int M.Album -> (Int, String, Int, Int, String, Int) -> Map.Map Int M.Album (4)
groupAlbum acc (albumId, albumName, artistId, trackId, trackName, trackDuration) =
case (Map.lookup albumId acc) of
Nothing -> Map.insert albumId (M.Album (Just albumId) albumName artistId [M.Track (Just trackId) trackName trackDuration]) acc
Just _ -> Map.update (\a -> Just (addTrack a (trackId, trackName, trackDuration))) albumId acc
where
addTrack album (trackId, trackName, trackDuration) =
album {M.albumTracks = (M.albumTracks album) ++ [M.Track (Just trackId) trackName trackDuration]}

newAlbum :: Sql.Connection -> M.Album -> IO M.Album (5)
newAlbum conn album = do
Sql.executeNamed conn "insert into album (name, artist_id) values (:name, :artistId)" [":name" := (M.albumName album), ":artistId" := (M.albumArtistId album)]
albumId <- lastInsertRowId conn
tracks <- zipWithM (\t i -> newTrack conn (i, fromIntegral albumId, (M.albumArtistId album), t)) (M.albumTracks album) [0..]

return album { M.albumId = Just $ fromIntegral albumId
, M.albumTracks = tracks
}

newTrack :: Sql.Connection -> (Int, Int, Int, M.Track) -> IO M.Track (6)
newTrack conn (trackNo, albumId, artistId, track) = do
Sql.executeNamed conn "insert into track (name, duration) values (:name, :duration)" [":name" := (M.trackName track), ":duration" := (M.trackDuration track)]
trackId <- lastInsertRowId conn
Sql.execute conn "insert into album_track (track_no, album_id, track_id) values (?, ?, ?)" (trackNo, albumId, trackId)

return track {M.trackId = Just $ fromIntegral trackId}

1
Function to list all albums

2
Function to list albums filtered by artist

3
Helper function to construct an album query with an optional where clause. The query returns a product
of albums and their tracks. Let’s just call this a performance optimization to avoid n+1 queries :-)

4
Since album information is repeated for each track, we need to group tracks per album. This part was a fun challenge
for a Haskell noob. I’m sure it could be done eveny more succinct, but I’m reasonably happy with the way it turned out.

5
This is the function to create a new album with all it’s tracks. We assume the tracks are sorted in the order they
should be persisted and uses zipWith to get a mapIndexed kind of function so that we can generate the appropriate track_no
for each album_track in the db.

6
Working with tracks we have to consider both the track and album_track entities in the db. As it is, the
album_track table is just overhead, but we knew that allready given the design decission taken earlier. Once we need to support
the fact that a track can be included in more that one album, we need to rethink this implementation.

Adding albums to the API

backend/src/Api.hs

type AlbumAPI = (1)
QueryParam "artistId" Int :> Get '[JSON] [M.Album] (2)
:<|> ReqBody '[JSON] M.Album :> Post '[JSON] M.Album
:<|> Capture "albumId" Int :> ReqBody '[JSON] M.Album :> Put '[JSON] M.Album
:<|> Capture "albumId" Int :> Get '[JSON] M.Album
:<|> Capture "albumId" Int :> Delete '[] ()

albumsServer :: Sql.Connection -> Server AlbumAPI
albumsServer conn =
getAlbums :<|> postAlbum :<|> updateAlbum :<|> getAlbum :<|> deleteAlbum

where
getAlbums artistId = liftIO $ case artistId of (3)
Nothing -> S.findAlbums conn
Just x -> S.findAlbumsByArtist conn x
postAlbum album = liftIO $ Sql.withTransaction conn $ S.newAlbum conn album
updateAlbum albumId album = liftIOMaybeToEither err404 $ Sql.withTransaction conn $ S.updateAlbum conn album albumId
getAlbum albumId = liftIOMaybeToEither err404 $ S.albumById conn albumId
deleteAlbum albumId = liftIO $ Sql.withTransaction conn $ S.deleteAlbum conn albumId

type API = "artists" :> ArtistAPI :<|> "albums" :> AlbumAPI (4)

combinedServer :: Sql.Connection -> Server API (5)
combinedServer conn = artistsServer conn :<|> albumsServer conn

1
We’ve added a new API type for Albums

2
For listing albums we support an optional query param to allow us to filter albums by artist

3
This implementation is quite simplistic, we probably want to provide a more generic way to handle multiple
filter criteria in the future.

4
The API for our backend is now a composition of the api for artists and the api for albums

5
As Servant allows us to compose apis it also allows us to compose servers (ie the implementations of the apis).
We create a combined server, which is what we ultimately expose from our backend server

The really observant reader might have noticed that the update function for albums is a little bit more
restrictive/solid than the corresponding function for artist. Here we actually check if the given album id
corresponds to a album in the DB. If it doesn’t we return a 404.

backend/Main.hs

app :: Sql.Connection -> Application
app conn = serve A.api (A.combinedServer conn) (1)

main :: IO ()
main = do
withTestConnection $ \conn -> do
B.bootstrapDB conn (2)
run 8081 $ albumCors $ app conn

1
Rather than serve the just the albumServer, we now serve the combined server.

2
We’ve updated bootstrapping to use the the new bootstrap module

Backend summary

That wasn’t to hard now was it ? Adding additional end points was quite straightforward, the hard part
was overcoming analysis paralysis. Settling on data types and db design took some time, and in hindsight I might
have opted for a more simplistic db design. I’m also curious about how the design would have been had I started top down (frontend first)
and backend last. I have a strong suspicion it would have been different.

Haskell IO
The thing I probably spent most time struggling with was working with IO actions. Apparantly I shouldn’t
use the term IO Monad. Anyways I can’t wrap my head around
when I’m "inside" the IO thingie and when I’m not. It’s obvious that do, , let and return is something
I have to sit down and understand (in the context of IO things). My strategy of trial and error doesn’t scale
all that well, and whatsmore It feels ackward not having a clue on the reasoning on why something is working or not.
Note to self, read up on Haskell IO.

REST concerns
Even with this simple example I started to run into the same old beef I have with generic rest endpoints.
They rarely fit nicely with a Single Page Application. They work ok when it comes to adding and updating data,
but when it comes to querying it all becomes much more limiting. In a SPA you typically want much more flexibility
in terms of what you query by and what you get in return.

  • In an album listing for a given artist I might just want to display the name, release date, number of songs and album length
    I’m not interested in the tracks.
  • In an album listing / album search outside of an artist context I probably want to display the artist name
  • For a mobile client I might just want to display the album name (size of payloads might actually be important for mobile…​)
  • Likewise when listing artists I might want to display number of albums
  • Or when searching I might want to search album name, artist name and/or track name

Reading about GraphQL, Falcor
and more recently Om next has been an eye-opener to me.
The ideas here rings true and bodes well for the frontend, probably soonish something will materialize for Elm too.
But what to do on the server side I wonder ?

Frontend

New routes

frontend/src/Routes.elm

type Route (1)
= Home
-- ...
| AlbumDetailPage Int
| NewArtistAlbumPage Int
| EmptyRoute

routeParsers = (2)
[ static Home "/"
-- ...
, dyn1 AlbumDetailPage "/albums/" int ""
, dyn1 NewArtistAlbumPage "/artists/" int "/albums/new"
]

encode route = (3)
case route of
Home -> "/"
-- ...
AlbumDetailPage i -> "/albums/" ++ toString i
NewArtistAlbumPage i -> "/artists/" ++ (toString i) ++ "/albums/new"
EmptyRoute -> ""

1
We have added 2 new routes, one for edit/create albums, one for creating a new album (for a given artist)
(actually there is a 3 for creating an album without selecting an artist, but it’s not wired up yet)

2
We need to add route matchers for the new routes.

3
We also need to add encoders for our new routes.

Service API

To call our new REST api for albums we need to implement a few new functions and json decoders.
We’ll only show two of the api related functions.

type alias AlbumRequest a = (1)
{ a | name : String
, artistId : Int
, tracks : List Track
}

type alias Album = (2)
{ id : Int
, name : String
, artistId : Int
, tracks : List Track
}

type alias Track = (3)
{ name : String
, duration : Int
}

getAlbumsByArtist : Int -> (Maybe (List Album) -> a) -> Effects a (4)
getAlbumsByArtist artistId action =
Http.get albumsDecoder (baseUrl ++ "/albums?artistId=" ++ toString artistId)
|> Task.toMaybe
|> Task.map action
|> Effects.task

createAlbum : AlbumRequest a -> (Maybe Album -> b) -> Effects.Effects b (5)
createAlbum album action =
Http.send Http.defaultSettings
{ verb = "POST"
, url = baseUrl ++ "/albums"
, body = Http.string (encodeAlbum album)
, headers = [("Content-Type", "application/json")]
}
|> Http.fromJson albumDecoder
|> Task.toMaybe
|> Task.map action
|> Effects.task

-- other functions left out for brevity. Check out the sample code or have a look at episode 2 for inspiration

-- Decoders/encoders for albums/tracks (6)

albumsDecoder : JsonD.Decoder (List Album)
albumsDecoder =
JsonD.list albumDecoder

albumDecoder : JsonD.Decoder Album
albumDecoder =
JsonD.object4 Album
("albumId" := JsonD.int)
("albumName" := JsonD.string)
("albumArtistId" := JsonD.int)
("albumTracks" := JsonD.list trackDecoder)

trackDecoder : JsonD.Decoder Track
trackDecoder =
JsonD.object2 Track
("trackName" := JsonD.string)
("trackDuration" := JsonD.int)

encodeAlbum : AlbumRequest a -> String
encodeAlbum album =
JsonE.encode 0 <|
JsonE.object
[ ("albumName", JsonE.string album.name)
, ("albumArtistId", JsonE.int album.artistId)
, ("albumTracks", JsonE.list <| List.map encodeTrack album.tracks)
]

encodeTrack : Track -> JsonE.Value
encodeTrack track =
JsonE.object
[ ("trackName", JsonE.string track.name)
, ("trackDuration", JsonE.int track.duration)
]

1
We use the AlbumRequest type when dealing with new albums

2
The Album type represents a persisted album

3
We aren’t really interested in the id of tracks so we only need one Track type

4
For finding albums for an artist we can use the Http.get function with default settings

5
To implement createAlbum we need to use Http.Send so that we can provide custom settings

6
Decoding/Encoding Json to/from types isn’t particularily difficult, but it is a bit of boilerplate involved

The album page

We’ve made some changes to the ArtistDetail page which we won’t show in this episode.
These changes include:

  • List all albums for an artist
  • Add features to remove album and link from each album in listin to edit the album
  • A button to initation the Album detail page in "Create New" mode

albumdetails

We consider an Album and it’s tracks to be an aggregate. This is also reflected in the implementation
of the ArlbumDetail module in the frontend code. You’ll hopefully see that it’s not that hard
to implement a semi advanced page by using the composability of the elm architecture.

Ok lets look at how we’ve implemented the Album detail page and it’s associated track listing.

Types

type alias Model = (1)
{ id : Maybe Int
, artistId : Maybe Int
, name : String
, tracks : List ( TrackRowId, TrackRow.Model )
, nextTrackRowId : TrackRowId
, artists : List Artist
}

type alias TrackRowId = (2)
Int

type Action (3)
= NoOp
| GetAlbum (Int)
| ShowAlbum (Maybe Album)
| HandleArtistsRetrieved (Maybe (List Artist))
| SetAlbumName (String)
| SaveAlbum
| HandleSaved (Maybe Album)
| ModifyTrack TrackRowId TrackRow.Action
| RemoveTrack TrackRowId
| MoveTrackUp TrackRowId
| MoveTrackDown TrackRowId

1
The model kind of reflects the Album type we saw in the previous chapter, but it’s
bespoke for use in this view. Most notably we keep a list of Artists (for an artist dropdown) and
tracks are represented as a list of trackrow models from the TrackRow.elm module.

2
To be able to forward updates to the appropriate TrackRow instance we are using a sequence type

3
There are quite a few actions, But the last 4 are related to the list of TrackRows.

AlbumDetails can be seen as holding an AlbumListing, updates that concerns the list is handled
by AlbumDetails whilst updates that concerns individual TrackRows are forwarded to the appropriate
TrackRow instance.

The update function

update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
NoOp ->
( model, Effects.none )

GetAlbum id -> (1)
( model
, Effects.batch
[ getAlbum id ShowAlbum
, getArtists HandleArtistsRetrieved
]
)

ShowAlbum maybeAlbum -> (2)
case maybeAlbum of
Just album ->
( createAlbumModel model album, Effects.none )

-- TODO: This could be an error if returned from api !
Nothing ->
( maybeAddPristine model, getArtists HandleArtistsRetrieved )

HandleArtistsRetrieved xs -> (3)
( { model | artists = (Maybe.withDefault [] xs) }
, Effects.none
)

SetAlbumName txt -> (4)
( { model | name = txt }
, Effects.none
)

SaveAlbum -> (5)
case (model.id, model.artistId) of
(Just albumId, Just artistId) ->
( model
, updateAlbum (Album albumId model.name artistId (createTracks model.tracks)) HandleSaved
)
(Nothing, Just artistId) ->
( model
, createAlbum { name = model.name
, artistId = artistId
, tracks = (createTracks model.tracks)
} HandleSaved
)
(_, _) ->
Debug.crash "Missing artist.id, needs to be handled by validation"

HandleSaved maybeAlbum -> (6)
case maybeAlbum of
Just album ->
( createAlbumModel model album
, Effects.map (\_ -> NoOp) (Routes.redirect <| Routes.ArtistDetailPage album.artistId)
)

Nothing ->
Debug.crash "Save failed... we're not handling it..."

RemoveTrack id -> (7)
( { model | tracks = List.filter (\( rowId, _ ) -> rowId /= id) model.tracks }
, Effects.none
)

MoveTrackUp id -> (8)
let
track =
ListX.find (\( rowId, _ ) -> rowId == id) model.tracks
in
case track of
Nothing ->
( model, Effects.none )

Just t ->
( { model | tracks = moveUp model.tracks t }
, Effects.none
)

MoveTrackDown id -> (9)
let
track =
ListX.find (\( rowId, _ ) -> rowId == id) model.tracks

mayMoveDown t =
let
idx =
ListX.elemIndex t model.tracks
in
case idx of
Nothing ->
False

Just i ->
i < ((List.length model.tracks) - 2)
in
case track of
Nothing ->
( model, Effects.none )

Just t ->
( { model
| tracks =
if (mayMoveDown t) then
moveDown model.tracks t
else
model.tracks
}
, Effects.none
)

ModifyTrack id trackRowAction -> (10)
let
updateTrack ( trackId, trackModel ) =
if trackId == id then
( trackId, TrackRow.update trackRowAction trackModel )
else
( trackId, trackModel )
in
( maybeAddPristine { model | tracks = List.map updateTrack model.tracks }
, Effects.none
)

1
When we mount the route for an existing album, we need to retrieve both the album and
all artists (for the artist dropdown). To do both in one go we can use Effects.batch

2
We use the album param to differntiate between "update" and "new" mode for albums. If show album is called with an album we update our inital model with the information
contained in the given album (this also involves initating TrackRow.models for each album track.
If there is no album, we just add an empty track row and the initiate the retrieval of artists for the artists dropdown.

3
Once artists are retrieved we update our model to hold these

4
This action is executed when the user changes the value of the name field

5
The save action either calls update or create in the server api based on whether the model has an albumId or not.
In both instances it needs to convert the model to an Album/AlbumRequest as this is what the signature of the ServerApi functions require

6
A successful save will give an Album type back, we update the model and in this instance we
also redirect the user to the artist detail page.

7
This action is called when the user clicks on the remove button for a track row. We’ll get back to this when
in just a little while

8
Action to move a track one step up in the track listing. If it’s already at the top
it’s a no op. The "heavy" lifting is done in the moveUp generic helper function

9
Similar to MoveTrackUp but it has addtional logic to ensure we don’t move a track below the
always present empty (Pristine) row in the track listing

10
The ModifyTrack action forwards to the update function for the TrackRow in question. Each track row is
tagged with an Id (TrackRowId)

The view

view : Signal.Address Action -> Model -> Html (1)
view address model =
div
[]
[ h1 [] [ text <| pageTitle model ]
, Html.form
[ class "form-horizontal" ]
[ div
[ class "form-group" ]
[ label [ class "col-sm-2 control-label" ] [ text "Name" ]
, div
[ class "col-sm-10" ]
[ input
[ class "form-control"
, value model.name
, on "input" targetValue (\str -> Signal.message address (SetAlbumName str))
]
[]
]
]
, ( artistDropDown address model )
, div
[ class "form-group" ]
[ div
[ class "col-sm-offset-2 col-sm-10" ]
[ button
[ class "btn btn-default"
, type' "button"
, onClick address SaveAlbum
]
[ text "Save" ]
]
]
]
, h2 [] [ text "Tracks" ]
, trackListing address model
]

artistDropDown : Signal.Address Action -> Model -> Html (2)
artistDropDown address model =
let
val =
Maybe.withDefault (-1) model.artistId

opt a =
option [ value <| toString a.id, selected (a.id == val) ] [ text a.name ]
in
div
[ class "form-group" ]
[ label [ class "col-sm-2 control-label" ] [ text "Artist" ]
, div
[ class "col-sm-10" ]
[ select
[ class "form-control" ]
(List.map opt model.artists)
]
]

trackListing : Signal.Address Action -> Model -> Html (3)
trackListing address model =
table
[ class "table table-striped" ]
[ thead
[]
[ tr
[]
[ th [] []
, th [] []
, th [] [ text "Name" ]
, th [] [ text "Duration" ]
, th [] []
]
]
, tbody [] (List.map (trackRow address) model.tracks)
]

trackRow : Signal.Address Action -> ( TrackRowId, TrackRow.Model ) -> Html (4)
trackRow address ( id, rowModel ) =
let
context =
TrackRow.Context
(Signal.forwardTo address (ModifyTrack id))
(Signal.forwardTo address (always (RemoveTrack id)))
(Signal.forwardTo address (always (MoveTrackUp id)))
(Signal.forwardTo address (always (MoveTrackDown id)))
in
TrackRow.view context rowModel

1
The view function for the page.

2
The artist dropdown (a github star for the observant reader that can spot what’s missing :-) )

3
Generates the track listing for the album

4
The rendering of each individual TrackRow is forwarded to the TrackRow module. We pass on a
context so that a TrackRow is able to "signal back" to the AlbumDetails page for the actions
that are owned by AlbumDetails (RemoveTrack, MoveTrackUp and MoveTrackDown). You’ll see how that
plays out when we look at the TrackRow implementation in the next secion.

Why the context thingie ?

Well we can’t have the AlbumDetails depending on TrackRows and the TrackRow component having a dependency
back to AlbumDetails. To solve that we pass on the tagged forwarding addresses so that TrackRows can signal
AlbumDetails with the appropriate actions. I guess you can sort of think of them as callbacks, but it’s not quite that.

Another slightly more elaborate explantion might be that when a user performs something on a track row that
we capture (say a click on the remove button). The view from the track row returns a signal (wrapped as an effect) to album details which
in turn returns a signal back to main. The signal is processed by the startapp "event-loop" and flows back through
the update functions (main → AlbumDetails) and since it’s tagged to as an action to be handled by AlbumDetails is handled
in AlbumDetails update function (and doesn’t flow further.

Clear as mud or perhaps it makes sort of sense ?

Track row

Types

type alias Model = (1)
{ name : String
, durationMin : Maybe Int
, durationSec : Maybe Int
, status : Status
}

type alias Context = (2)
{ actions : Signal.Address Action
, remove : Signal.Address ()
, moveUp : Signal.Address ()
, moveDown : Signal.Address ()
}

type Status (3)
= Saved
| Modified
| Error
| Pristine (4)

type Action (5)
= SetTrackName String
| SetMinutes String
| SetSeconds String

1
The model captures information about an album track. Duration is separated into
minutes and seconds to be more presentable and easier for the user to input. In addition
we have a status flag to be able to give the user feedback and handle some conditional logic.

2
Here you see the type definition for the Context we previously mentioned we used in the when
forwarding view rendering for each individual track row in the Album Details page. (Btw it could be any
component as long as they pass on a context with the given signature of Context).

3
The possible status types a row can be in.

4
Prisitine has a special meaning in the track listing in AlbumDetails. It should always be just one and it should be the last row.
However that’s not the responsibility of TrackRow. TrackRow should just ensure the status is correct at all times.

5
The possible actions that TrackRow handles internally

Update function

update : Action -> Model -> Model
update action model =
case action of
SetTrackName v -> (1)
{ model | name = v, status = Modified }

SetMinutes str -> (2)
let
maybeMinutes = Result.toMaybe <| String.toInt str
in
case maybeMinutes of
Just m ->
{ model | durationMin = maybeMinutes, status = Modified }

Nothing ->
if String.isEmpty str then
{ model | durationMin = Nothing, status = Modified}
else
model

SetSeconds str -> (3)
let
maybeSeconds = Result.toMaybe <| String.toInt str
in
case maybeSeconds of
Just m ->
if m < 60 then
{ model | durationSec = maybeSeconds, status = Modified }
else
model

Nothing ->
if String.isEmpty str then
{ model | durationSec = Nothing, status = Modified}
else
model

1
Updates the trackname model property when user inputs into the trackname field

2
Updates the minutes property if a valid number is entered. Also blanks the field
when the text input field becomes empty

3
Similar to minutes, but also ensures that you don’t enter more than 59 !

View

We’ll only show parts of the view to limit the amount of code you need to scan through.

view : Context -> Model -> Html
view context model =
tr
[]
[ td [] [ statusView model ]
, td [] [ moveView context model ]
, td [] [ nameView context model ]
, td [] [ durationView context model ]
, td [] [ removeView context model ]
]

nameView : Context -> Model -> Html
nameView context model =
input
[ class "form-control"
, value model.name
, on "input" targetValue (\str -> Signal.message context.actions (SetTrackName str)) (1)
]
[]

removeView : Context -> Model -> Html
removeView context model =
button
[ onClick context.remove () (2)
, class <| "btn btn-sm btn-danger " ++ if isPristine model then "disabled" else ""
]
[ text "Remove" ]

1
When a user causes an input event on the name input field we create a message using the address in context.actions with action SetTrackName
So this message will cause an update eventually forwarded to the update function of TrackRow

2
When a user clicks on the remove button we use the address given by context.remove with a payload of () (ie void).
This message will always be forwarded to the address for AlbumDetails with the payload set to RemoveTrack with the given track row id.
All of which TrackRow is blissfully unaware of.

Main.elm wiring it all up

type alias Model =
WithRoute
Routes.Route
{ --....
, albumDetailModel : AlbumDetail.Model
}

type Action
= NoOp
-- ...
| AlbumDetailAction AlbumDetail.Action
| RouterAction (TransitRouter.Action Routes.Route)

initialModel =
{ transitRouter = TransitRouter.empty Routes.EmptyRoute
-- ...
, albumDetailModel = AlbumDetail.init
}

mountRoute prevRoute route model =
case route of
-- ...

AlbumDetailPage albumId -> (1)
let
(model', effects) =
AlbumDetail.update (AlbumDetail.GetAlbum albumId) AlbumDetail.init
in
( { model | albumDetailModel = model' }
, Effects.map AlbumDetailAction effects)

NewArtistAlbumPage artistId -> (2)
let
(model', effects) =
AlbumDetail.update (AlbumDetail.ShowAlbum Nothing) (AlbumDetail.initForArtist artistId)
in
( { model | albumDetailModel = model' }
, Effects.map AlbumDetailAction effects)

-- ...

update action model =
case action of
-- ..

AlbumDetailAction act -> (3)
let
( model', effects ) =
AlbumDetail.update act model.albumDetailModel
in
( { model | albumDetailModel = model' }
, Effects.map AlbumDetailAction effects
)

-- ..

1
When we mount the route for the AlbumDetailsPage ("/albums/:albumId") we call the
update function of AlbuDetail with a GetAlbum action. You might remember that this in turn calls the functions
for retrieving an Album and the function for retrieving artists as a batch.

2
When the user performs an action that results in the NewArtistAlbumPage being mounted ("/artists/:artistId/albums/new")
, we call the update on AlbumDetail with ShowAlbum action and a reinitialized model where artistId is set.

3
In the update function of Main we forward any actions particular to AlbumDetail

Frontend summary

Working with the frontend code in Elm has been mostly plain sailing. I struggled a bit to get
all my ducks(/effects) in a row and I’m not too happy with some of the interactions related to new vs update.

Unfortunately the elm-reactor isn’t working all that well with 0.16, certainly not on my machine.
It also doesn’t work particularily well with single page apps that changes the url. I looked at and tried a couple of
alternatives and settled on using elm-server. I had to make some modifications
to make it work nicely with an SPA. I submitted a PR that seems to work nicely for my use case atleast.
With that in place, the roundtrip from change to feedback became very schneizz indeed !

Undoubtably there is quite a bit that feels like boiler plate. The addition of routing also introduces yet another thing you have
to keep in mind in several places. Boilerplate it might be, but it’s also quite explicit. I would imagine that in a large app you might grow a bit weary of some of the boilerplate and start looking for ways to reduce it.

I’d be lying if I said I’ve fully grasped; signals, tasks, ports, effects and mailboxes. But it’s gradually becoming clearer
and it’s very nice that you can produce pretty cool things without investing to much up front.

Concluding remarks

I utterly failed to make a shorter blog post yet again. To my defence, the default formatting of Elm
do favor newlines bigtime. Most of the Elm code has been formatted by elm-format btw.

I’m really starting to see the benefits of statically (strongly) typed functional languages. The journey
so far has been a massive learing experience. Heck this stuff has been so much fun, I ended up taking a day off work so that
I could work on this for a whole day with most of my good brain cells still at acceptable performance levels.
Shame I can’t use this stuff at work, but I’m starting to accumulate quite a substantial collection of selling points.

Whats next ?
The sample app has started to accumulate quite a bit of technical dept, so I suppose the next episode(s)
should start to address some of that.

Posted by Trouble (::)entrating on Mon, 02/29/2016 - 03:33

I’ve just released elm-http-extra 5.0.0, and along with the links I wanted to
share the reasons for the changes and discuss some of the challenges that I
faced addressing them.

A huge thank you to Fred Yankowski for using
elm-http-extra, finding a serious flaw in the design, talking through
it with me on the Elm Slack, and
generally being an awesome member of the Elm community!

The problem

Prior to this release, Http.Extra.send accepted three arguments: a
Json.Decode.Decoder for the value expected on success, another
Json.Decode.Decoder for the value expected on server error, and a
RequestBuilder to pipe with (|>) and kick off the request. (For more
information on this API design, see Chainable APIs with the forward apply operator). This is a significant
improvement over elm-http because it lets you deal with the body of a response
in the case of a server error. You might be dealing with an API that returns an
object like the following on error:

{
"message": "There was an error",
"status": 500,
"success": false
}

With elm-http the process for accessing this data as a record is fairly
laborious, and with elm-http-extra it is easy and also required. I thought
this was a pretty clever design until Fred told me about how his API worked. On
a 404 error, his API returned the following body:

"404 page not found"

The Task failed as expected, but instead of failing with a
BadResponse String and providing access to the message, it always failed with
UnexpectedPayload and an error message "Unexpected token p”, even though
the error body was being decoded with Json.Decode.string. The problem is that
"404 page not found" is not valid JSON. "\"404 page not found\"" with the
included quotes is valid JSON. The type of value being accepted from the body
needed to be lifted one level higher.

The solution

I spent some time thinking about this and decided that the signature for send
should remain roughly the same, but include some polymorphism for how bodies are
handled. The first attempt I made was to create a tagged union which could
express either a plain text string body or a JSON body and contain a decoder:

type BodyReader a
= StringReader
| JsonReader (Json.Decode.Decoder a)

This might raise an immediate red flag in the experienced Elm programmer’s mind,
as this encoding of the transform as a union type implicitly expresses the
output type of a function on that type. Ultimately I couldn’t get a rework using
this method to compile without writing one function for each combination of
BodyReader instances for error and success cases.

So I scrapped that plan and went one step outward: generalizing the idea of what
a BodyReader does at the function level. In this case, BodyReader is just a
function interface:

type alias BodyReader a =
Http.Value -> Result String a

This interface bears a lot of resemblance to Json.Decode.decodeString, in
which a string value goes in and a Result String a comes out. Each
BodyReader is allows to take on any kind of Http.Value and optionally fail.
This has a two-fold benefit.

  1. Even though Text is the only supported Http.Value type right now, this
    generic function interface leaves room for extensibility once more values are
    introduced.
  2. Since we only need to write a function, readers can get even more specific
    than just accessing the string value contained by Text. jsonReader does
    this, and anyone can do the same by Result.map-ing the output of
    stringReader.

So there it is! Now, instead of having to pass a Json.Decode.Decoder and
always getting a parsing error, use stringReader to get a string, or continue
decoding JSON with jsonReader.

Other changes

Max Goldstein pointed out that the signature of
withHeader accepting a (String, String) added more visual noise than was
necessary. He is absolutely right, and now withHeader accepts two string, one
for the key and one for the value. See the example in the next section for a
demo!

New README example

In this example, we expect a successful response to be JSON array of strings,
like:

["hello", "world", "this", "is", "the", "best", "json", "ever"]

and an error response might have a body which just includes text, such as the
following for a 404 error:

Not Found.

We’ll use HttpExtra.jsonReader and a Json.Decode.Decoder to parse the
successful response body and HttpExtra.stringReader to accept a string
body on error without trying to parse JSON.

import Time
import Http.Extra as HttpExtra exposing (..)
import Json.Decode as Json

itemsDecoder : Json.Decoder (List String)
itemsDecoder =
Json.list Json.string

addItem : String -> Task (HttpExtra.Error String) (HttpExtra.Response (List String))
addItem item =
HttpExtra.post "http://example.com/api/items"
|> withBody (Http.string "{ \"item\": \"" ++ item ++ "\" }")
|> withHeader "Content-Type" "application/json"
|> withTimeout (10 * Time.second)
|> withCredentials
|> send (jsonReader itemsDecoder) stringReader

Contributing to elm-http-extra

I’m always happy to receive any feedback and ideas about additional features or
anything at all! Any input and pull requests are very welcome and encouraged. If
you’d like to help or have ideas, get in touch with me at @luke_dot_js on
Twitter
or @luke in the Elm Slack!

Posted by Trouble (::)entrating on Sat, 02/27/2016 - 19:36

The (|>) operator is, in my opinion, one of the most elegant features of the
Elm language. It lets us read function applications in the order the actually
happen and this readability gain can have a hugely positive influence on our API
and application design. While this operator is generally useful for expressing
data transformations, it has a particularly nice fit for building large or
high-complexity configuration objects more expressively.

Consider as a primary
candidate the low-level request interface in elm-http. Let’s say we want to
send a PATCH request to a cross-origin endpoint and we need to include a
cookie for authentication and we only want to wait 5 seconds for the request to
complete. Doing so requires two configuration objects:

import Http

request : Http.Request
request =
{ verb = "PATCH"
, headers =
[ ("Origin", "http://lukewestby.com")
, ("Access-Control-Request-Method", "PATCH")
, ("Content-Type", "application/json")
]
, url = "https://exampleapi.com/items/4"
, body = Http.string (encodeItemUpdate itemUpdate)
}

settings : Http.Settings
settings =
{ Http.defaultSettings
| timeout = 5 * Time.second
, withCredentials = True
}

result : Task Http.RawError Http.Response
result =
Http.send settings request

This is a pretty obnoxious amount of work to send a request with parameters
that are not too far outside the norm. This is, of course, as it should be as
elm-http is intended to be a low-level interface to XMLHttpRequest in Elm
and not anything fancy or expressive. But as humans we want something that is
easy to read and understand. This is the intention of
elm-http-extra, and it will
serve as an excellent example of using (|>) to build configuration as needed
instead of supplying it all at once.
A roughly equivalent request using elm-http-extra looks like the following:

import Http.Extra as HttpExtra exposing (..)

result : Task (HttpExtra.Error ApiError) (HttpExtra.Response Item)
result =
HttpExtra.patch "https://exampleapi.com/items/4"
|> withHeader ("Origin", "http://lukewestby.com")
|> withHeader ("Access-Control-Request-Method", "PATCH")
|> withHeader ("Content-Type", "application/json")
|> withStringBody (encodeItemUpdate itemUpdate)
|> withTimeout (5 * Time.second)
|> withCredentials
|> send decodeItem decodeApiError

The advantages of the above are as follows:

  • We don’t need to know anything about the underlying configuration objects
  • We only need to learn or remember the functions which are relevant to the
    the task at hand
  • We can easily remove the final send and express the whole configuration
    building process as a separate function that is easy to test by doing
    equality comparison on the output.
  • We can combine existing operators into more high-level ones that are totally
    reusable.

As an example of the final point, we can extract the headers out into a separate
function that expresses the header needs of every API request and even use the
contents of the request configuration builder record to help us out:

import Http.Extra as HttpExtra exposing (..)

withApiHeaders : HttpExtra.RequestBuilder -> HttpExtra.RequestBuilder
withApiHeaders builder =
let
verb =
.verb (HttpExtra.toRequest builder)
in
builder
|> withHeader ("Origin", "http://lukewestby.com")
|> withHeader ("Access-Control-Request-Method", verb)
|> withHeader ("Content-Type", "application/json")

result : Task (HttpExtra.Error ApiError) (HttpExtra.Response Item)
result =
HttpExtra.patch "https://exampleapi.com/items/4"
|> withApiHeaders
|> withStringBody (encodeItemUpdate itemUpdate)
|> withTimeout (5 * Time.second)
|> withCredentials
|> send decodeItem decodeApiError

Using the (|>) operator in this way allows us to build totally extensible DSL-
like APIs without all of the complexity of an actual DSL because everything is
just a function with a very specific type of interface. This even allows us to
abstract actual DSLs in a much nicer and easier-to-understand way. Consider
regular expressions, the confusing DSL to end them all. Using this technique
with the
elm-verbal-expressions
package we can express a Regex using a human-readable, chainable interface.
Instead of constantly re-learning regular expressions to perform a simple task
like match a correctly formatted url, we can write the following:

import Regex exposing (Regex)
import VerbalExpressions as Verex exposing (..)

tester : Regex
tester =
VerbalExpressions.verex
|> startOfLine
|> followedBy "http"
|> possibly "s"
|> followedBy "://"
|> possibly "www."
|> anythingBut " "
|> endOfLine
|> toRegex

Every call in the pipeline above is just a function which operates on some
configuration builder which we can ultimately transform into a result which
would have been harder to obtain on its own. In the case of elm-http it was a
Task a b for the request result which took a lot of configuration, and in
this case it is a Regex which would have required a hard-to-understand
regular expression string.

Let’s try and generalize a few things about this pattern. The process of using a
chainable API with (|>) involves three stages, initialize, build, and
compile. During the initialize stage we start with generate a base object of a
particular type, called the builder. It could be a totally new record type or
a union type that wraps existing configuration types. In the case of
elm-http-extra it is a union type which holds an Http.Request and an
Http.Settings together. The value obtained during the initialize step should
always be valid for compilation straight away, which means several
initialization functions might be required.

This is the case with elm-http-extra since every request must have at least a
verb and a url. The case is much simpler with elm-verbal-expressions, since we
can start every VerbalExpression from a single base value. In this case we
just expose the one initial value and take advantage of immutability in Elm to
allow chaining from the basic case.

During the build step we make use of the (|>) operator by enforcing a specific
signature for the functions in our API. Every function must accept a builder as
its final argument and return a builder as its return value. Any additional
arguments should precede the builder. This data-last signature is a common style
in functional programming as it also makes function composition easy.

The compile step allows to actually make use of the builder. Compilation
functions can have any number of purposes, like extraction of data for testing
or actually running a Task. For example, in elm-http-extra there are three
compilation functions - toRequest, toSettings, and send. The first two
allow us to extract and inspect information from the builder whereas send
actually creates a Task. Compilation functions that do not create a Task can
be used during the build step as well since they are pure.

Summary

As the amount of configuration or mental overhead to perform a particular
operation increases it can be useful to dissociate the various components of
that configuration into a series of smaller, step-wise operations. Such an
effort is best expressed through functions that play to the strengths of the
(|>) operator. As examples, elm-http requires much configuration for many
common request cases, and regular expressions require a lot of research and
preparation to use correctly. Through the use of chainable APIs and then (|>),
elm-http-extra and elm-verbal-expressions make those processes easy to
read and maintainable. The success of this API design in these cases can be
easily applied to many similar use cases by simply following the initialize-
build-compile pattern.

Posted by crossingtheruby.com on Mon, 02/15/2016 - 02:00

On Wednesday last week I flew over to London from Amsterdam to attend the newly formed Elm London’s inaugural meetup. I was glad to have RSVPed early as the event quickly became oversubscribed with the waiting list outnumbering the attendee list three to one. That in itself is a clear indication of the kind of interest in Elm from all quarters.

Continue reading…

Posted by dennisreimann on Thu, 02/04/2016 - 13:00

By defining a union type one always creates a new type that did not exist before. A union type can be an amalgamation of different types – but it does not have to be.

Posted by dennisreimann on Mon, 02/01/2016 - 13:00

Records and tuples can contain an arbitrary amount of elements – as opposed to lists, arrays, sets, and dictionaries these elements do not have to be of the same data type.

Posted by dennisreimann on Wed, 01/27/2016 - 13:00

In Elm there are different kinds of data structures that can contain elements. This article spotlights the iterable structures lists, arrays, sets, and dictionaries.

Posted by dennisreimann on Thu, 01/21/2016 - 13:00

This article spotlights the central construct of the Elm programming language: Functions. What does the definition of a function look like, how can functions be chained via piping and what the heck is currying?