This tutorial assumes some knowledge of html and some css. I will often refer to the theory from the functional fundamentals series, so even if you've written some Elm programs you might find use in how this math can benefit you in practice.
This tutorial is intentionally fast. I always loathed the long sessions behind a replicator, so I've decided to be rather brief here. If you find it's going a little fast for you, you might want to look at Elm's official guide as well.
If you have any questions or criticism or if you find something unclear, feel free to comment away.
After so many posts about the theory of functional programming, it's about time I showed some actual code. Since career and freelancing advice seems to get a lot of attention on dev.to, I've decided to build an invoice generator.
I will assume you've setup your Elm programming environment and can compile/run the following hello world example:
module Main exposing (main) import Html exposing (Html) main : Html a main = Html.text "Hello, world!"
For those new to FP or Elm, let's go through it line-by-line.
module Main exposing (main)
declares that this file is a module called
Main and that the value
main is visible outside of this module.
import Html exposing (Html)
This is an import declaration. The
import Html part means we are importing the module called Html. The
exposing (Html) makes the type
Html directly visible from our code, without needing to specify the module name. I.e. it lets us write
Html rather than
main : Html a main = Html.text "Hello, world!"
These two lines denote the type and value, of the term called
main. The first line is called the type declaration, the second line is the term declaration. In a less math-oriented context, such as day to day programming, they are called function signature and function body, respectively.
The function body must exclusively result in a type that matches the function signature. If this is not the case, compilation will fail. This is the Curry-Howard isomorphism at work, ensuring that our program will give the right type.
The type in question is
Html a. Remember functors?
Html is one of them, it takes a type
a and makes a
Html a. In this case, the
a is still not bound to any type. Meaning we could treat
Html a as
Html Int, etc. We will ignore this for now as we're not going to use the
a part of
The value of main is
Html.text "Hello, world!". This means it is the result of the function
Html.text applied to the value
Let's check the documentation of
Html.text and see if we're indeed going to get the right type. You wouldn't usually do this, the compiler does it for you, but we'll do it now for didactic purposes.
The type of
String -> Html msg. I.e. it is a function that when given a value of type
String will produce a value of type
Html msg. In turn, this would make
main have type
Html msg. This might look like a type mismatch, but in Elm, lower-case type names mean "any type"1. So both
a have the same meaning (any type). In other words, the term
main has the right type.
main is a special value in Elm: it is your program's main entry point.
main can only have a restricted set of types, which includes
Htm a. If we use a
Html value, main will simply build a static website.
Remember that functional programming has no variables, only values. We are, in fact, not assigning a value to
main, we are defining what main is.
We're going to create an invoice template based on the Latex template dapper invoice by Michael Kropat.
The structure is as follows:
- One row with headers, two columns:
- company name
- invoice number and date
- One row with two columns:
- sender info, a list or rows with
- key and value pairs
- receiver info, a list of rows with
- key and value pairs
- sender info, a list or rows with
- A row with a left-aligned column
- A row with a table
- A row with a right-aligned column
Recall that it's better to write the most composable functions. Therefore, we're going to make small simple functions and later compose them.
The Latex template doesn't really do this, that's not what Latex is for, and suffers from it. For instance, my country has different legal requirements and taxation laws than the US, which I assume is what this template is based on. Because the template expected fixed arguments, rather than, say, a list of key-value pairs, I'd have to create an entire new template.
But enough rambling.
Let's make the simplest function first: one that takes a number, and converts it to a string of the form "INVOICE #<number>".
invoiceNrText : Int -> String invoiceNrText n = "INVOICE #" ++ String.fromInt n
++ is an operator, it's pretty much the same as a function that takes two arguments, but you write it between arguments rather than in front of it. In fact, you can enclose it in parentheses to make it act like a regular function. I.e.
a ++ b is the same as
(++) a b.
Using the documentation for String.fromInt and (++) operator, you should at this point be able to figure out the meaning of these lines. Note that functions bind stronger than operators, so the line is equivalent to
"INVOICE #" ++ ( String.fromInt n )
Let's add the
invoiceNrText funtion to our module and change
main's term declaration:
main = Html.text ( invoiceNrText 7 )
Note that the parentheses are not used to represent function evaluation, as common in imperative paradigms. Rather they declare that we apply
invoiceNrText and apply the result to
Html.text. Had we written
Html.text invoiceNrText 7, we would apply
Html.text and subsequently apply 7 to the result.
So far so good, but we'd like to use html tags, not just plain text. Let's make a function that turns the string from
invoiceNrText into an h1 html element, we use Html.h1:
invoiceNrHeader : String -> Html a invoiceNrHeader str = Html.h1  [ Html.text str ]
The function signature of
List (Attribute msg) -> List (Html msg) -> Html msg, meaning it takes a list of attributes
List (Attribute msg), and yields a function with type
List (Html msg) -> Html msg2. So when we apply the empty list
Html.h1, we are left with a new function, to which to apply
[ Html.text str ], a list of html elements
List (Html msg), and we're left with a
Functions in Elm are curried. Rather than applying all arguments at once, we apply them one by one and return new functions as intermediate results.
We can now replace
Html.text in the main function with
Now is as good a time as any for a throwback to function composition. We've seen the
∘ operator in category theory, used for arrow composition. In Elm that is
<<. We can use this to write the main function's body as
main = ( invoiceNrHeader << invoiceNrText ) 7. By doing so, we first create a new function, the composition of
invoiceNrText, and then apply 7 to it.
We can just do
invoiceNrHeader ( invoiceNrText 7 ), so there's little point to function composition in this case, but there's situations in which it comes in handy so it's good to know ahead of time.
Ok, but we want to add some style to this. We can do that with the
For this we'll need to import a new module:
import Html.Attributes as Att
as Att parts lets us write
Att rather than
Html.Attributes. I'm lazy and don't like writing full module names. Of course, we could also just expose all the functions we need.
Let's replace the empty list inside
[ Att.style "font-weight" "400" , Att.style "font-size" "2.5rem" , Att.style "letter-spacing" "7px" ]
(See documentation for
Voila, your list of attributes.
Although you could use external css, I personally think it makes no sense for Elm, as inlining css makes it possible to unify structure and layout in reusable functions, much like webcomponents. I'm also going to abuse it for demonstrative purposes in just a moment.
The full code should now look like this:
module Main exposing (main) import Html exposing (Html) import Html.Attributes as Att main : Html a main = invoiceNrHeader ( invoiceNrText 7 ) invoiceNrText : Int -> String invoiceNrText n = "INVOICE #" ++ String.fromInt n invoiceNrHeader : String -> Html a invoiceNrHeader str = Html.h1 [ Att.style "font-weight" "400" , Att.style "font-size" "2.5rem" , Att.style "letter-spacing" "7px" ] [ Html.text str ]
If you've managed to follow along thus far, you've gained a good grip on the basics. I think it's about time to throw you into the deep end, of a lake filled with alligators.
I'll change our imports to:
import Html exposing (..) import Html.Attributes exposing (..)
This exposes all the functions of
Html.Attributes modules. This is generally bad practice, as it makes it harder to see where names are coming from. In this case it is acceptable, because most programmers will recognize the exposed names, so repeated module names merely fill the screen with uninformative symbols.
I want to separate styling such as colors and font types from layout. In particular, parent elements should determine the layout/position of their children, but children should be responsible for their own styles. Doing this the right way would require building an entire new layout spec, and people have done just that. But we're not doing things the right way, we're going to do them the
wrong educative way.
So, I want a function that will allow me to define my element in one place, and add style to it somewhere else (its parent). Unfortunately, once you have an
Html a value, there's not much you can do with it. Instead we're going to define a function
makeStylabe that constructs a
List (Attribute a) -> Html a, so that we can apply style to it later.
Take a moment to parse this type signature, as it is a good exercise, but don't feel obliged to fully understand it before reading on.
makeStylable : ( List (Attribute a) -> List (Html a) -> Html a ) --tag -> List (Attribute a) --attributes -> List (Html a) --children -> List (Attribute a) --later attributes -> Html a
The body of makeStylable:
makeStylable tag attributes childElements laterAttributes = tag (laterAttributes ++ attributes) childElements
This function allows us to write e.g.:
makeStylable h1 [style "color" "red"] [text "foo"]
which would result in a
List (Attribute a) -> Html a (remember partial application/currying). Exactly what we want.
Let's complete the right header. I'll represent the date as a String, as dates are a can of worms the opening of which is not relevant for the purpose of this tutorial.
dateHeader : String -> List (Attribute a) -> Html a dateHeader str = makeStylable h5 [ style "color" "DarkGrey" , style "font-weight" "400" , style "font-size" "1.25rem" ] [ text str ] rightHeaderLayout : ( List (Attribute a) -> Html a ) -> ( List (Attribute a) -> Html a ) -> List (Attribute a) -> Html a rightHeaderLayout topHtml bottomHtml = makeStylable div [ style "area" "number-header" , style "text-align" "center" ] [ topHtml [ style "margin" "0" , style "padding" "0" ] , bottomHtml [ style "margin-top" "-10px" ] ]
After changing the main function to use our new html, the full code should look something like this:
module Main exposing (main) import Html exposing (..) import Html.Attributes exposing (..) main : Html a main = rightHeaderLayout ( invoiceNrHeader ( invoiceNrText 17 ) ) ( dateHeader "1 April 2012" )  invoiceNrText : Int -> String invoiceNrText n = "INVOICE #" ++ String.fromInt n makeStylable : ( List (Attribute a) -> List (Html a) -> Html a ) --tag -> List (Attribute a) --attributes -> List (Html a) --children -> List (Attribute a) --later attributes -> Html a makeStylable tag attributes childElements laterAttributes = tag (laterAttributes ++ attributes) childElements invoiceNrHeader : String -> List (Attribute a) -> Html a invoiceNrHeader str = makeStylable h1 [ style "font-weight" "400" , style "font-size" "2.5rem" , style "letter-spacing" "7px" ] [ text str ] dateHeader : String -> List (Attribute a) -> Html a dateHeader str = makeStylable h5 [ style "color" "DarkGrey" , style "font-weight" "400" , style "font-size" "1.25rem" ] [ text str ] rightHeaderLayout : ( List (Attribute a) -> Html a ) -> ( List (Attribute a) -> Html a ) -> List (Attribute a) -> Html a rightHeaderLayout topHtml bottomHtml = makeStylable div [ style "area" "number-header" , style "text-align" "center" ] [ topHtml [ style "margin" "0" , style "padding" "0" ] , bottomHtml [ style "margin-top" "-10px" ] ]
Since this tutorial is already rather long, I'll break it off here for now. Although we've covered only very little of the invoice, we've covered most of the basic principles, which we will recycle in future parts.
There are some exceptions, such as
number, which have specific meanings -
Floatin the case of
number- but they are rare and usually self-explanatory.
->is right associative.
- This does not necessarily mean that a new function is allocated for every argument. I'm describing a conceptual situation which the compiler is free to implement and optimize in various ways.