A type for views
Tags: elm
Published: 2018-07-02 13:00:43

---
title: A type for views
published: true
description: 
tags: elm
canonical_url: https://blog.noredink.com/post/175470670558/a-type-for-views
series: Using Elm types outside the Model
---

*This post originally appeared on the [NoRedInk blog](https://blog.noredink.com/post/175470670558/a-type-for-views).*


One of the joys of writing Elm apps is the many ways the compiler catches our mistakes, and the useful error messages it gives us when it does. It's a two-way street though, meaning that the degree to which the compiler can weed out mistakes depends on what information we disclose to it. In other words, we have to help the compiler help ourselves. In this post we'll see that this is truer for the view function than anywhere else, and we'll see how we can help the compiler help us get our view functions right.

## A book recommendation application

Suppose we're building a web page showing the latest book releases of your favorite authors, ordered by popularity. The main page of the app is a simple list of books, listing the title, author name, and the ISBN of each book. Lets create a model for this application.

```elm
type alias Model =
    { authors : List Author
    }


type alias Author =
    { name : String
    , books : List Book
    , favorite : Bool
    }


type alias Book =
    { title : String
    , isbn : String
    , popularity : Int
    }
```

Next up is the view function of this application. For this view we need to transform the data from our model a bit. First of all we're not interested in displaying books of authors the user has not favorited, so we can filter those out. Secondly, we want to order the books of the remaining authors by popularity. Thirdly we want to display items on the page that contain data from the `Book` (title, isbn) and `Author` (name) types.

## Intermezzo: Are we sure we want to do this?

But wait, maybe instead of writing a transformation function it would be easier to change our `Model` type to more closely resemble the structure of the page! For example, suppose our model looks like this:

```elm
type alias Model =
    { books : List Book
    }


type alias Book =
    { authorName : String
    , authorIsFavorite : Bool
    , title : String
    , isbn : String
    , popularity : Int
    }

```

Now our view function will be much easier to write. But this model comes with a disadvantage. Two books of the same author can disagree on whether that author is one of the users favorites. The previous model prevented this impossible state because the `favorite` status of an author was only stored in a single place. When faced with this trade-off we prefer our model to prevent as many impossible states as possible, even if our view function will need do some transformations.

## View transformations at NoRedInk

Many of the Elm apps we have at NoRedInk have a model that looks quite different from the Html we draw on the screen. As a result our view functions will need to transform data, but that's not a problem, especially if the compiler helps make sure we're transforming correctly. But there's the rub: we'll need to work a little bit to get the compiler to help us here.

To understand why, let's look at a transformation present in every Elm app: The update function that transforms an old model state into a new one. The compiler helps us a lot with this one, it ensures that regardless of what happens our update function will always return a valid new model, which prevents all sorts of bugs. But when it comes to the view function we get no such guarantee, the compiler only ensures it returns Html. If all goes well that Html will look like our specs, but it might as well be the image of a cat as far as the compiler is concerned. Both are Html and so equally valid results of calling our view function.

To prevent such bugs we put some extra constraints on what we consider valid output from a view function. We can do this by defining a new type describing the shape of our page. Our view function we then split into two parts. First we transform our model into this `View` type, this is the hard part. Then we take that type and render it into Html. Because the type was designed to be similar to the structure of the page, this second part should be easy.

## Let's transform some books
Let's get back to our book recommendation app and write a `View` type for it.

```elm
type alias View =
    List BookView


type alias BookView =
    { authorName : String
    , title : String
    , isbn : String
    , popularity : Int
    }
```

Now we can write the transformation function that turns our `Model` into a `View` type. This is the hardest part of the job, but because the transformation takes place between two specialized types `Model` and `View`, we will get a lot of help from the Elm compiler.

```elm
toView : Model -> View
toView model =
    model.authors
        |> List.filter .favorite
        |> List.concatMap booksForAuthor


booksForAuthor : Author -> List BookView
booksForAuthor author =
    author.books
        |> List.map
            (\{ title, isbn, popularity } ->
                { authorName = author.name
                , title = title
                , isbn = isbn
                , popularity = popularity
                }
            )
        |> List.sortBy .popularity
```

Finally, all that's left is to write the view function that renders our `View` type to `Html`.

```elm
view : Model -> Html msg
view model =
    toHtml (toView model)


toHtml : View -> Html msg
toHtml books =
    Html.ul [] (List.map viewBook books)


viewBook : BookView -> Html msg
viewBook book =
    Html.li []
        [ Html.text ("Author: " ++ book.authorName)
        , Html.text ("Title: " ++ book.title)
        , Html.text ("ISBN: " ++ book.isbn)
        ]
```

## Conclusion

We've seen how we can use a `View` type to write our view function using some extra help from the compiler.

As a nice side-effect, this approach also makes it easy to unit-test our view logic. We can write ordinary Elm unit tests for our `toView` function, which contains all of our actual transformation logic. This allows us to write less tests against our `Html` returning view function, which although possible is harder.


![](https://i.imgur.com/jiCpoq9.png)
**Stöffel**
[@schtoeffel](https://twitter.com/schtoeffel)
Engineer at [NoRedInk](http://noredink.com)

![](https://i.imgur.com/J491Oom.png)
**Jasper Woudenberg**
[@jasperwoudnberg](https://twitter.com/jasperwoudnberg)
Engineer at [NoRedInk](http://noredink.com)