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.
Introduction
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.
We're going to be using Elm, you can install it locally or follow along online on Ellie. At the time of writing Elm is at version 0.19.
Hello, world!
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 Html.Html
.
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 String
, Html Int
, etc. We will ignore this for now as we're not going to use the a
part of Html a
.
The value of main is Html.text "Hello, world!"
. This means it is the result of the function Html.text
applied to the value "Hello, world!"
.
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 Html.text
is 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 msg
and 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.
Invoice base design
We're going to create an invoice template based on the Latex template dapper invoice by Michael Kropat.
We're ignoring the arrow because, while cool, it is a bit too complicated.
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
The first header
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 7
to invoiceNrText
and apply the result to Html.text
. Had we written Html.text invoiceNrText 7
, we would apply invoiceNrText
to 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 Html.h1
is 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 msg
2. So when we apply the empty list []
to 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 Html msg
3.
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 invoiceNrHeader
.
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 invoiceNrHeader
after 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 Attribute.style
function.
For this we'll need to import a new module:
import Html.Attributes as Att
the 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 invoiceNrHeader
with:
[ Att.style "font-weight" "400"
, Att.style "font-size" "2.5rem"
, Att.style "letter-spacing" "7px"
]
(See documentation for style
functions).
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 ]
Adding the sub-header
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
and 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.
Footnotes
-
There are some exceptions, such as
number
, which have specific meanings -Int
orFloat
in the case ofnumber
- 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.
Top comments (0)