I gave a talk at ElixirConf 2015 on combining the Phoenix web framework with the Elm programming language. This is the tutorial that was referred to in that talk.
The tutorial walks through the creation of a very basic seat saving application, like one you'd use when booking a flight for example. The application will do just enough to demonstrate the mechanisms for getting the two technologies talking to each other.
There is an accompanying repo for this tutorial. Each of the numbered steps has an associated commit so that you can just look at the diffs if you'd rather not read through the whole thing.
Type annotations
So far Elm has been happily inferring the types that we are using in our application, and it will continue to do so. However let's take a moment to look at how we can make it more obvious to others who might read our code what types we are expecting. We can do this by using type annotations.
Type annotations are optional in Elm, but they help us, and others that read our code, to better see what is going on. They also allow us to specify the contract for our functions.
A type annotation goes on the line before a function definition and consists of the name of that function, followed by a colon, followed by a list of one or more types. The list of types is separated by ->
. The very last type in this list is always the return type. Elm functions always return one value, so there is always one return type. The other types refer to the type of each parameter being passed into the function.
For example,
samesies : Int -> String -> Bool
samesies number word =
(toString number) == word
The samesies
function takes two arguments, one of type Int
and one of type String
and returns a value of type Bool
. Its type annotation is
samesies : Int -> String -> Bool
.
Let's add type annotations to our existing functions.
Add the following above the
main
function:main : Html.Html String
Our main function takes no arguments and returns an Html String, i.e. collection of HTML represented as String values.
We're having to prefix the Html type with
Html.
here because theHtml
function is defined in theHtml
library. To save us from having to do that each time, let's tweak the Html import so we use the(..)
syntax instead rather than naming each function that we want to use.import Html exposing (..)
Now we can change our
main
function's type annotation tomain : Html String
The next function is the
init
function (we don't have to add type annotations to type definitions as they already state their expected types). Theinit
function takes no arguments and returns a Model.init : Model
The
view
function takes a Model as an argument and returns an Html String.view : Model -> Html String
Last but not least, the
seatItem
function takes a Seat as an argument and returns an Html String.seatItem : Seat -> Html String
The end result should look like this:
module SeatSaver exposing (..) import Html exposing (..) import Html.Attributes exposing (class) main : Html String main = view init -- MODEL type alias Seat = { seatNo : Int , occupied : Bool } type alias Model = List Seat init : Model init = [ { seatNo = 1, occupied = False } , { seatNo = 2, occupied = False } , { seatNo = 3, occupied = False } , { seatNo = 4, occupied = False } , { seatNo = 5, occupied = False } , { seatNo = 6, occupied = False } , { seatNo = 7, occupied = False } , { seatNo = 8, occupied = False } , { seatNo = 9, occupied = False } , { seatNo = 10, occupied = False } , { seatNo = 11, occupied = False } , { seatNo = 12, occupied = False } ] -- VIEW view : Model -> Html String view model = ul [ class "seats" ] (List.map seatItem model) seatItem : Seat -> Html String seatItem seat = li [ class "seat available" ] [ text (toString seat.seatNo) ]
Checking the browser again, nothing should have changed.
Type annotations, as mentioned above, are optional. Elm will infer our types for us. However it is good to get into the habit of using them. I find that they help me to figure out what a function should be doing. They also help to catch errors when defining functions in case we accidentally use a type that is not the one we were intending.
For example, let's change our view
function to the following:
view : Model -> Html String
view model =
List.map seatItem model
When you try to compile this, you will see the following error in your terminal window where the server is running:
Elm has fantastic error messages. Here it quite clearly tells us that "The type annotation for view
does not match its definition.". What this is telling us is that the view
function is expected to return a value of type Html String
but has returned a value of List Html
instead.
Returning the view
function to its original definition will fix this error.
view : Model -> Html String
view model =
ul [ class "seats" ] (List.map seatItem model)
Summary
Now that we know more about Type Annotations it will be easier for us to understand the update
function. We'll look at that in Part 6.