Quickstart Elm 0.19, part 7

Understanding Currying in Elm

By: Ajdin Imsirovic 26 October 2019

In the Elm language, all the functions are curried. Curried functions can be partially applied when called. In this article, we’ll look at what that means.

A close-up of a race track with an Elm logo overlaid

Note: Examples in this article use Elm 0.19.

What is Currying in Elm?

Any function in Elm that takes n number of arguments, can be described as a function that takes 1 argument and returns a function that takes n-1 number of arguments.

It works like this. Imagine we have a function that takes 2 arguments:

concatTwoWords one two =
    one ++ two

What we see above is the function definition for our new function, concatTwoWords.

In order to partially apply the above function, when we call it, we’ll pass it only one argument.

Once we get to call our concatTwoWords, that first argument — one — will take the value of "Hello", of type String.

Next, we’ll define another function, greeting, in terms of our partially applied concatTwoWords:

greeting = concatTwoWords "Hello"

Again, what you see above is the definition of the greeting function.

All that is left to do for us now, is to call the greeting function, and supply it with the other parameter that concatTwoWords was expecting:

main =
    text (greeting " World")

Let’s see all of the above wrapped up in a simple Elm app:

module Main exposing (main)

import Html exposing (Html, text)


concatTwoWords one two =
    one ++ two
    
greeting = concatTwoWords "Hello"


main : Html msg
main =
    text (greeting " World")

You can play with the above example on Ellie app.

To reiterate, let’s make the above example a bit more abstract.

It’s pretty easy, there are only three steps to remember:

  • define a function with n arguments (we’ll name it the nArgs function)
  • define another function in terms of the first function being partially applied, that is, having only one argument passed to it (we’ll name it the nArgsMinus1 function)
  • call the nArgsMinus1 function, and pass it the remaining arguments that the nArgs function was expecting)

Let’s see the above three steps in an app:

module Main exposing (main)

import Html exposing (Html, text)

nArgs one two three four five =
    one + two + three + four + five
    
nArgsMinus1 =
    nArgs 2 3 4 5

main : Html msg
main =
    text (Debug.toString (nArgsMinus1 1))

First, we define the nArgs function.

We follow it up with defining the nArgsMinus1 function as a partially applied nArgs function.

Finally, we call the nArgsMinus1 and pass it just a single argument.

Concatenate Strings Using Partial Application in Elm 0.19

First off, a quick reminder: Partial application occurs when we call a function and pass it a fewer number of parameters than the number of parameters that that function expects.

For example, in the below app, we begin by defining the concatFiveWords function, which takes five parameters (five words to be concatenated: wOne, wTwo, etc).

module Main exposing (main)
import Html exposing (Html, text)

concatFiveWords wOne wTwo wThree wFour wFive =
    wOne ++ wTwo ++ wThree ++ wFour ++ wFive

presetFirstWord = concatFiveWords "Lorem"
presetFirstTwoWords = presetFirstWord " Ipsum"
presetFirstThreeWords = presetFirstTwoWords " Dolor"
presetFirstFourWords = presetFirstThreeWords " Sit"
presetFirstFiveWords = presetFirstFourWords " Amet"

main =
    -- text (presetFirstFourWords " Whatever")
    -- text (presetFirstThreeWords " Latin" " Jibberish")
    -- text (presetFirstTwoWords " is" " made-up" "text")
    text (presetFirstWord " something" " something" " yadda" " yadda")
    -- text (concatFiveWords "This" " is" " something" " completely" " different")

After defining the concatFiveWords function, we define a new function, called presetFirstWord, which we define in terms of partially applying the concatFiveWords function. Notice how when we call the presetFirstWord function (in the non-commented-out second-to-last line of code) the presetFirstWord function will take the remaining four arguments.

Then, we introduce another new function, we name it presetFirstTwoWords, and we define it in terms of partially applying the presetFirstWord function. When called, the presetFirstTwoWords will take the remaining three arguments.

As you can see in the above code, we introduce more functions that are defined in terms of partially applying the previously defined function.

Finally, we can see how to call any one of those functions in the main function.

Feel free to have a look at the above app live on Ellie app.

Another Example to Drive the Point Home

Here’s another example app.

module Main exposing (main)

import Html exposing (..)


concatFiveWords wOne wTwo wThree wFour wFive =
    wOne ++ wTwo ++ wThree ++ wFour ++ wFive
    
presetFirstWord  = concatFiveWords "Lorem"
presetFirstTwoWords = presetFirstWord " Ipsum"
presetFirstThreeWords = presetFirstTwoWords " Dolor"
presetFirstFourWords = presetFirstThreeWords " Sit"
presetFirstFiveWords = presetFirstFourWords " Amet"

one = text (presetFirstFiveWords)
two = text (presetFirstFourWords " Whatever")
three = text (presetFirstThreeWords " Latin" " Jibberish")
four = text (presetFirstTwoWords " Are" " Imaginary" " Words")
five =  text (presetFirstWord " Bla" " Bla" " Yadda" " Yadda")
six = text (concatFiveWords " I" " am" " making" " words" " up")
    
main : Html msg
main =
    div [] [ h1 [] [ one ]
           , h2 [] [ two ]
           , h3 [] [ three ]
           , h4 [] [ four ]
           , h5 [] [ five ]
           , h6 [] [ six ]
           , p [] [ text (presetFirstFourWords " Sasquatch") ]
           ]

This app is also available live on Ellie app.

Revisiting our definition of partial application

Let’s try improving our definition of partially applied functions in Elm.

First we’ll write the updated definition, then we’ll test it in an app:

Any function in Elm that takes X number of arguments, can be described as a function that takes Y number of arguments and returns a function that takes X-Y number of arguments.

In other words, if a function takes 5 arguments, we can describe it as a function that takes 3 arguments and returns a function that takes 5–3 number of arguments.

Here is an example:

module Main exposing (main)

import Html exposing (Html, text)

-- X is 5
functionWithXArgs a b c d e =
    a ++ b ++ c ++ d ++ e
    
-- Y is 3
functionWithYArgs =
    functionWithXArgs "a " "c " "e "

main : Html msg
main =
    text (Debug.toString (functionWithYArgs "b " "d "))

The result of above code is: “a c e b d “.

Interestingly, we only need to call the correct number of arguments in both functions. The order of the arguments called is obviously not important in the example above.

You can find it on Ellie app too.

Curried Functions, Type Definitions, and Higher Order Functions

Higher order functions are those that can take other functions as parameters.

Higher order functions are also those that return functions as their return values.

In Elm, every function takes just a single parameter (and possibly returns a function).

This means that partially applied functions are higher order functions.

Let’s look at using type annotations to help us with grasping this concept even better.

Head on over to [elmrepl.cuberoot.in] and type the following:

concatFiveWords a b c d e = a ++ (b ++ (c ++ (d ++ e))

The Repl will return the below annotation:

<function>
    : appendable -> appendable -> appendable -> appendable -> appendable -> appendable

In other words, the function we named concatFiveWords takes an appendable, and:

  • returns a function that takes an appendable, which also
  • returns a function that takes an appendable, which likewise
  • returns a function that takes an appendable, which expectedly
  • returns a function that takes an appendable, which finally
  • returns an appendable.

Yet Another Way to Define Currying

A function that takes N number of arguments is curried if you transform it into a series of functions that take only 1 argument.

As we can see above, if a function takes N arguments, we can partially apply it so that it is expressed in terms of N-1 functions that each take 1 argument.

Looking at the concatFiveWords function again, this time as a simple app, we can finally understand what’s happening it this function’s type signature, on line 6:

1
2
3
4
5
6
7
8
9
10
11
12
13
module Main exposing (main)

import Html exposing (Html)


concatFiveWords : String -> String -> String -> String -> String -> String
concatFiveWords a b c d e =
    a ++ b ++ c ++ d ++ e


main : Html msg
main =
    Html.text (concatFiveWords "What" " a" " lovely" " day" " today")

With the exception of the right-most arrow sign ->, all the arrow signs -> on line 6 in the type annotation above can be read as “…and returns a function that takes a…”.

The right-most arrow is read: “… and returns a…”, which is then followed with the type of the returned value.

In other words, this is how we’d read the type annotation on line 6:

Function concatFiveWords takes a String

…and returns a function that takes a String …which returns a function that takes a String …which returns a function that takes a String …which returns a function that takes a String …which returns a String.

Now we Finally Understand Type Annotations!

Each type annotation represents a series of functions that each take a single value whose type is specified in between the ->. The last arrow shows the actual type of the value returned.

Note that the number of arguments passed to the function is always equal to the number of arrows in the type annotation. The additional type at the very right edge of the type annotation is the actual return value, i.e. the value of the evaluated expression that is found to the right of the = sign in the function definition.

In the next article, we’ll look into function composition in Elm 0.19.

Feel free to check out my work here: