Some time ago I was able to listen to the episode We’re Teaching Functional Programming Wrong of the podcast Corecursive. Richard Feldman tells of a purely functional language with which you can actually create dynamic web pages. It is not even necessary to understand what a Monad is. Since I consider object-oriented programming to be overrated*The talk [Free your Functions](https://www.youtube.com/watch?v=WLDT1lDOsb4) by Klaus Iglberger highlights some practical aspects my point of view., I had a closer look at this approach. In this post I will report on my first steps, hurdles and progress in Elm and focus on aspects that were unfamiliar to me having an imperative background.

Elm installation and development environment

You can, e.g., use the package manager Npm like me or alternatively download binaries. As an IDE I use VS Code. I also installed elm-format via Npm so that VS Code can format my source code via Shift-Alt-F. With

elm make src/File.elm --output app.js

you compile an Elm source file to JavaScript. The product can be integrated into an HTML page via

<div id="app"></div>
<script src="app.js"></script>
<script>
    Elm.Calc.init({node: document.getElementById("app")});
</script>

Private retirement provision

Since private pension provision is currently an issue for us, I built a calculator as a mini test project. You pass the annual interest rate, the monthly savings rate, and the number of savings years and it spits out the account balance at the end of the savings period. You can see the result in the following box, which I have included here using <iframe></iframe>.

In the following, the focus is neither on the formulas nor on a basic introduction to Elm. Instead I present a few for imperatively programming beings*Artificial intelligences are included. like me *I have programmed a lot with Python and C++ in the past. I have almost no experience with JavaScript. As a student, I used JavaScript snippets via copy+paste to exchange button images during mouse hover events. And recently I put together a small Nuxt frontend. unusual aspects. A basic introduction to Elm can be found e.g. in the Elm-Guide. I also had a look at the book Elm in Action*Attention! This is NOT an affiliate link! Attention! purchased. My complete code is on Github.

The last element

The standard sequential data structure in Elm is a list. In contrast to Python lists, there is no random access. So you cannot access the i-th element of the list via someList[i]. You cannot access the last element of the list neither. Since I needed that for about the whole time, the following little function last was created.

last : List Float -> Float
last inputs =
    Array.fromList inputs 
        |> Array.get (List.length inputs - 1) 
        |> Maybe.withDefault - 1

In the first two lines we can see the signature of the function. A list of inputs is expected as an argument. From this a floating point number is calculated. As the imperatively programming being may have noticed, brackets around arguments and a return keyword are missing. Neither is required in Elm. The value of the expression is returned directly without return. In order to return the last element of the list, we convert the list into an Array that allows random access in Elm. One might wonder if you could save the copy by just using an array all the time. However, other useful functions such as map2, range or sum that I have used elsewhere are not implemented for the type Array in Elm.

Functions are composed with |>, i.e. $$ (f \circ g) (x) = f (g (x)) $$ can be implemented in Elm as g x |> f.

One of my favorite features in Elm is that partial function application is directly available. For example, if you have defined a function

def g(x, y): [...]

in Python, you can define via

f=partial(g, x=2)

another function f in y that equals g for x=2. In Elm, an additional higher-order function such as partial from the Python module functools is not necessary for partial function application. The partial application of the function g x y = [...], i.e., the definition of a function in y with fixed x = 2, is simply g 2.

Coming back to the function last, we see that Array.fromList converts a List into an Array and passes the array to the partially applied function Array.get (List.length inputs - 1).

The function

Array.get: Int -> Array a -> Maybe a

expects an integer and an array with elements of type a as arguments and returns the element at the position defined by the integer parameter. Hence, the partially applied function Array.get (List.length inputs - 1) returns the last element of the array. Strictly speaking, the result is not the last element in the list but a Maybe, an optional value that may or may not exist. In this post, I do not care about clean error handling, e.g., in the case of an empty list. So I assume the existence of the last element and simply unpack the optional type with the partially applied function Maybe.withDefault -1. This returns a -1 in the event of an error.

Testing and debugging

From Elm-programmers*Here, too, I include artificial intelligences. But I have not yet met any Elm-programming artificial intelligence. I have read the sentence “If it compiles, it works”. That sounds great, but strictly speaking it goes too far. Of course, the compiler cannot detect logic errors in the program. The easiest way to validate the program’s correctness that I have found is to use the Npm package elm-test. With the imports

import Expect
import Test exposing (..)

you can write the following test code for the function last.

testLast : Test
testLast =
    describe "last"
        [ test "last with 2 elts"
            (\_ -> Expect.equal (last [ 0, 4 ]) 4)
        , test "last with 1 elts"
            (\_ -> Expect.equal (last [ 4 ]) 4)
        , test "last with 10 elts"
            (\_ -> Expect.equal (last (List.repeat 9 0 ++ [ 4 ])) 4)
        , test "last with 0 elts"
            (\_ -> Expect.equal (last []) -1)
        ]

Here, we see another typical feature of functional programming, namely anonymous lambda functions. To understand the notation, consider the example \x -> x * x. This function calculates the square of its argument x. The lambda function occurring in the test code

\_ -> Expect.equal (last [ 0, 4 ]) 4)

does not expect any arguments and is a valid second parameter of the function test.

I have not yet found a sensible way to debug Elm programs interactively. I was able to stop at breakpoints in the generated JavaScript. But I couldn’t make sence of that. If you want to debug with output and without breakpoints, you can use Debug.log. The function returns its second argument and also logs it into the console. So if you replace line 5 in the above snippet with

(\_&nbsp;->&nbsp;Expect.equal&nbsp;(Debug.log&nbsp;"last&nbsp;of&nbsp;[0,&nbsp;4]"&nbsp;(last&nbsp;[&nbsp;0,&nbsp;4&nbsp;]))&nbsp;4)

the test still works because the second argument of Debug.log is simply returned. The first argument is of the type String and serves as a description of the log. When you run the test, you get the output

last of [0, 4]: 4.

Debug.log can also be used in program parts that are not test code.

Constants and variables

In order to be able to create a local constant in a function, the keywords let and in can be used. As expected, there are no variables in Elm that are not constant. For example, you could rewrite the function last with a temporary variable as follows, which I have only done here for purely demonstrative purposes.

last : List Float -> Float
last inputs =
    let
       inputsArr = Array.fromList inputs
    in
    Array.get (List.length inputs - 1) inputsArr 
        |> Maybe.withDefault -1

The let-in construct can be nested. Within the let part, in addition to constants, functions can also be defined that are only visible in the in part.

For-loops und if-expressions

There are no loops in Elm. If possible, you can use functions such as map, filter or foldl. The analogue to foldl is called reduce in Python. If the given situation cannot be represented by map, filter, foldl or the like, recursive functions must be used instead of loops. We can see an example in the following snippet.

linearPrices : Float -> Int -> Float -> List Float
linearPrices rate nYears startPrice =
    let
        recurse : Int -> List Float -> List Float
        recurse y prices =
            let
                newPrices =
                    linearPricesOfYear rate (last prices)
            in
            if y < nYears then
                newPrices ++ recurse (y + 1) newPrices

            else
                []
    in
    recurse 0 [ startPrice ]

In an intermediate step of the calculator, the linearPrices function converts an annual interest rate into fictitious exchange rates which are named prices. Thus, we can use the calculator with slight adjustments with simulated or historical stock prices. A recursive function is defined in the let-block, which in an imperative language could have been written as a loop. The termination condition is found in line 10, the increment of the counter variables in line 11. The list newPrices is calculated in the inner let block. When looking at the termination condition, the imperative programmer could stumble across the question: “What does the function recurse return?” The answer is that if-else expressions always return a value just like the ternary?: Operator in C++. Further, and as already mentioned, Elm does not know the keyword return. For instance, if the termination condition is met, the empty list [] is returned.

Types

You can define a so-called record by

type alias Model =
    { rate : Float
    , regularPayment : Float
    , nYears : Int
    }

In Python, a NamedTuple is perhaps most similar, even if the notation reiminds of a dict. With type alias you give an existing type a new name. In contrast, we define the new type Msg in the following snippet.

type Msg
    = ChangedRate String
    | ChangedRegPay String
    | ChangedYears String

This type is reminiscent of an enum-type in Python or C++ on the first sight. However, in addition to its value, i.e., either ChangedRate, ChangedRegPay, or ChangedYears, an instance of Msg can contain something more. In this case, this is a string for each of the values. The following function demonstrates the use of our new and our newly named type with the help of the case expression.

update : Msg -> Model -> Model
update msg model =
    case msg of
        ChangedRate newContent ->
            { model
                | rate =
                    String.toFloat newContent
                        |> Maybe.withDefault 0
            }

        ChangedRegPay newContent ->
            { model
                | regularPayment =
                    String.toFloat newContent
                        |> Maybe.withDefault 0
            }

        ChangedYears newContent ->
            { model
                | nYears =
                    String.toInt newContent
                        |> Maybe.withDefault 0
            }

Let me mention for the imperative programmer that case returns a value without using a return keyword. The value of msg is checked in the case-expression of the update function. Let us assume w. l. o. g. that msg has the value ChangedRate. Then,

    case msg of
        ChangedRate newContent ->
            { model
                | rate =
                    String.toFloat newContent
                        |> Maybe.withDefault 0
            }

is evaluated. ChangedRate newContent -> allows access to the string contained in msg via the name newContent. The value of newContent is converted into a floating point number and added to a Model-record with a new value for the rate-field. The rest is copied from the model-argument. This is done through*For exactly this purpose, I often use a self-written function in Python that copies a `NamedTuple` and only replaces the separately passed arguments. I have happily discovered that in Elm this is simply a language feature.

{ model | rate = [...] }.

We see again that a Maybe is being unpacked. When converting a string to a float, things can of course go wrong. In the event of an error, a 0 is simply returned here. This can also be seen directly in the calculator above. If you type a letter in one of the fields, the value is set to 0.

Dynamic web applications

I have been talking all the time about individual Elm constructs that are unfamiliar to me as an imperative programmer. Now in this section I would like to briefly explain how dynamic web frontends can be created with Elm. For more precise, better and more extensive explanations, I refer again to the Elm Guide.

Knowledge of HTML is helpful in the following*I am amused that my HTML knowledge, which I acquired as a student, is still relevant 25 years later.. To render the front end, a function view and a function update are required. we met the latter already in the last section. The view function that renders the calculator from above looks as follows.

view : Model -> Html Msg
view model =
    div []
        [ label [ for "rate" ] [ text "Interest rate in %" ]
        , br [] []
        , input
            [ id "rate"
            , value (String.fromFloat model.rate)
            , onInput ChangedRate
            ]
            []
        , br [] []
        , label [ for "regpay" ] [ text "Payment each month" ]
        , br [] []
        , input
            [ id "regpay"
            , value (String.fromFloat model.regularPayment)
            , onInput ChangedRegPay
            ]
            []
        , br [] []
        , label [ for "years" ] [ text "Years" ]
        , br [] []
        , input
            [ id "years"
            , value (String.fromInt model.nYears)
            , onInput ChangedYears
            ]
            []
        , br [] []
        , div [ style "font-weight" "bold" ]
            [ text
                (String.fromFloat
                    ((finalBalance (1 + (model.rate / 100))
                        model.regularPayment
                        model.nYears
                        * 100
                        |> round
                        |> toFloat
                     )
                        / 100
                    )
                )
            ]
        ]

The HTML functions such as div, label, input and br define the appearance and are imported from the Html modules*And once again Elm and I agree. The Elm module is called `Html` and not `HTML` although it is an abbreviation. For Camel-Case I prefer this notation because of the better readabilitye, e.g., of `HtmlButton` compared to `HTMLButton`.. They each expect two arguments, a list of attributes and a list of children. In our view-function the layout is defined and a variable part of the web page is filled with the argument model. The return value of the view function is of the type Html Msg. Msg is a so-called type variable, comparable to templates in C++ or generics in the programming language, who must not be named*It starts with J and and does not end with avaScript or ulia. Greetings to Nathan.. So Msg is also a type itself. The content of the Msg instance is defined by the triggered event handler in one of the input fields. That is, if there is w. l. o. g. a change in the rate field, onInput ChangedRate is triggered and the Msg instance with the value ChangedRate is sent to the update function. In addition, the Msg instance contains the value of the value attribute. The update-function adjusts the argument model according to the change in msg as seen above in the definition. And the model is then sent back to the view-function to display the updated status. To render the UI the update-and view-functions must be registered in a main-function.

main =
    Browser.sandbox { init = init, update = update, view = view }

An initial Model is passed as init parameter. That’s basically it.

This blog post was originally written in German and translated with Google Translate and some light-minded and manual post-processing.