Overview

Elm Friday: Functions (Part V)

No Comments

Elm is a functional language, so naturally, functions and function calls are pretty important. We have already seen some functions in the previous episodes. This episode goes into more detail regarding function definition and function application.

About This Series

This is the fifth post in a series of short and sweet blog posts about Elm. The stated goal of this series is to take you from “completely clueless about Elm” to “chief Elm guru”, step by step. If you have missed the previous episodes, you might want to check out the table of contents.

Functions

This is a function definition in Elm:

multiply a b = a * b

The function definition starts with the name of the function, followed by the parameter list. As opposed to C-style languages like Java, JavaScript and the likes, there are no parentheses or commas involved, the parameters are only separated by spaces.

The equals character = separates the function name with the parameter list from the function body. The function body can be any Elm expression that produces a value. We can reference the parameters in the function body.

Function definitions can use other functions in their body. This is how this looks like:

square a = multiply a a

Here, we define a square function by using the multiply function we defined earlier. Function calls also don’t need any parentheses, just list the function parameters you want to pass into the function separated by whitespace.

You do need parentheses when you have nested expresssions:

productOfSquares a b = multiply (square a) (square b)

You can also declare anonymous functions (also known as lambda expressions) on the fly:

incrementAll list = List.map (\ n -> n + 1) list

This thing wrapped in (\ and ) is an anonymous function that takes one parameter and returns the parameter, incremented by one. This anonymous function is then applied to all elements in a list by using List.map.

Actually, we could have written this shorter. The following is equivalent:

incrementAll2 = List.map (\ c -> c + 1)

Why is that the same? Because Elm supports something called currying. incrementAll defines a function that takes a list and produces another list. incrementAll2 also defines a function, but it is a function that takes no arguments and returns another function.

So when we write incrementAll2 [1, 2, 3] Elm first evaluates incrementAll2, gets a function and then procedes to put the remaining arguments ([1, 2, 3] in this case) into this function. The result is the same.

If you find it hard to wrap your head around currying, don’t worry about it too much for now. You can always resort to write a more verbose version of your function without currying and come back to this concept later. As a rule of thumb, if the last element in the parameter list in the function declaration is simply repeated at the end of the function body (like list in this case), you can probably omit both.

Let’s wrap this up. Here is a complete, working Elm program that uses the functions we defined above:

import Html

multiply a b = a * b

square a = multiply a a

productOfSquares a b = multiply (square a) (square b)

incrementAll list = List.map (\ c -> c + 1) list

incrementAll2 = List.map (\ c -> c + 1)

main = Html.p []
  [ Html.text ("3 × 5   = " ++ (toString (multiply 3 5)))
  , Html.br [] []
  , Html.text ("4²      = " ++ (toString (square 4)))
  , Html.br [] []
  , Html.text ("2² × 3² = " ++ (toString (productOfSquares 2 3)))
  , Html.br [] []
  , Html.text ("incrementAll [1, 2, 3] = " ++ (toString (incrementAll [1, 2, 3])))
  , Html.br [] []
  , Html.text ("incrementAll2 [1, 2, 3] = " ++ (toString (incrementAll2 [1, 2, 3])))
  ]

What happens in these lengthy expressions in the main function? Well, the functions we defined return mostly numbers (or lists, in the case of incrementAll). So we need to convert their results into strings via the toString function (which comes from the Basics package and is imported by default). We then use ++ to append the resulting string to a string literal ("3 × 5 = ", for example) and use Html.text to convert the string into an HTML text node.

Fancy Function Application

Whoa, did you see what we did there to bring the result of one of our functions to the screen? Let’s take a look at Html.text ("3 × 5 = " ++ (toString (multiply 3 5))) for a moment. That’s a lot of parentheses right there. Elm has two operators, |> and <|, to write expressions like that in a more elegant fashion.

  • |>: Take the expression to the left of the operator and put it into the function on the right hand side.
  • <|: Take the expression to the right of the operator and put it into the function on the left hand side.

Here is the main function of the previous program, rewritten with the new operators:

main = Html.p []
  [ Html.text <| "3 × 5   = " ++ (toString <| multiply 3 5)
  , Html.br [] []
  , Html.text <| "4²      = " ++ (toString <| square 4)
  , Html.br [] []
  , Html.text <| "2² × 3² = " ++ (toString <| productOfSquares 2 3)
  , Html.br [] []
  , Html.text <| "incrementAll [1, 2, 3] = " ++ (toString <| incrementAll [1, 2, 3])
  , Html.br [] []
  , Html.text <| "incrementAll2 [1, 2, 3] = " ++ (toString <| incrementAll2 [1, 2, 3])
  ]

If you like to go a bit crazy with this, you can even rewrite

Html.text <| "3 × 5   = " ++ (toString <| multiply 3 5)

as

Html.text <| (++) "3 × 5   = " <| toString <| multiply 3 5

or

square 4 |> toString |> (++) "4²      = " |> Html.text

Here we used the infix operator ++ as a non-infix function to be able to apply it with <| and |>. We also used a bit of currying again: (++) actually takes two arguments (the two strings that are to be concatenated). The expression (++) "3 × 5 = " is a partial function application, that is, we provide the first of the two arguments to yield a new function that takes only one argument, prepending "3 × 5 = " to everything that is passed to it.

To read code like this with ease, just imagine this line as the ASCII art represenation of a data pipeline. In |> style pipelines, data flows from left to right, in <| style pipelines, data flows from right to left. So, for example, to decipher a line like Html.text <| (++) "3 × 5 = " <| toString <| multiply 3 5, you start at the end (multiply 3 5), push this into the toString function to convert the number into a string, the resulting string the goes into the append function ((++)) together with the string literal and finally the concatenated string goes into the Html.text function.

This concludes the fifth episode of this blog post series on Elm. Make sure to check out the next episode, where we will take a look at type annotations.

Comment

Your email address will not be published. Required fields are marked *