DEV Community

DrBearhands
DrBearhands

Posted on

Invoices in Elm, part 1

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!"
Enter fullscreen mode Exit fullscreen mode

For those new to FP or Elm, let's go through it line-by-line.

module Main exposing (main)
Enter fullscreen mode Exit fullscreen mode

declares that this file is a module called Main and that the value main is visible outside of this module.

import Html exposing (Html)
Enter fullscreen mode Exit fullscreen mode

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!"
Enter fullscreen mode Exit fullscreen mode

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.

dapper invoice example
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
  • 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
Enter fullscreen mode Exit fullscreen mode

++ 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 )
Enter fullscreen mode Exit fullscreen mode

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 ]
Enter fullscreen mode Exit fullscreen mode

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 msg2. 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 msg3.

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
Enter fullscreen mode Exit fullscreen mode

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"
    ]
Enter fullscreen mode Exit fullscreen mode

(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 ]
Enter fullscreen mode Exit fullscreen mode

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 (..)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The body of makeStylable:

makeStylable tag attributes childElements laterAttributes =
    tag (laterAttributes ++ attributes) childElements
Enter fullscreen mode Exit fullscreen mode

This function allows us to write e.g.:

makeStylable h1 [style "color" "red"] [text "foo"]
Enter fullscreen mode Exit fullscreen mode

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"
        ]
    ]
Enter fullscreen mode Exit fullscreen mode

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"
        ]
    ]
Enter fullscreen mode Exit fullscreen mode

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

  1. There are some exceptions, such as number, which have specific meanings - Int or Float in the case of number - but they are rare and usually self-explanatory.
  2. -> is right associative.
  3. 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)