Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Functions in Elm

Elm is the simplest language I know. Whenever possible, there's only one way of doing things.

Functions in elm can only take one argument. For example:

\n -> n + 1

That function takes one number, called n and returns the next number. The function is an anonymous function, but we can give it a name:

addOne = \n -> n + 1

Remember that in elm there's only one way of doing things? Well, this is an exception, because we can also define the function addOne the following way:

addOne n = n + 1

We can annotate the type for the function addOne as:

addOne : Int -> Int

This means that it takes any value of type Int and returns an Int.


"But wait," you may be thinking, "what's the point of a language where functions can take only one argument? That's useless!"

Actually, that follows from the "only one way of doing things" premise. As it turns out, we can define a function that takes two arguments using two functions that take only one argument each. For example, we can define a function that takes an Int, and returns a function that takes another Int and returns an Int.

add = \n -> \m -> n + m

This function can also be written:

add = \n m -> n + m
add n m = n + m

How would we annotate the type for add?

add : Int -> (Int -> Int)

As it turns out, the -> (a.k.a. arrow) is right associative, which means that whenever a type expression using arrows has no parenthesis, the compiler infers parenthesis from right to left.

That's confusing, so let's see some examples. The following types are equivalent:

add : Int -> (Int -> Int)
add : Int -> Int -> Int

We can also take any function from the elm/core library as an example. For example, Maybe.map:

map : (a -> b) -> Maybe a -> Maybe b
map : (a -> b) -> (Maybe a -> Maybe b)

This also applies to functions that take more arguments:

f : a -> b -> c -> d -> e -> f
f : a -> (b -> (c -> (d -> (e -> f))))

Calling functions in elm is easy, we just say the name of the function and the argument, separated by a space:

addOne 2 -- returns 3

All functions take only one argument, so that's all there is to know when calling functions.

A function like add, that returns a function, can be called like this:

add 1 -- returns a function equivalent to addOne: \m -> 1 + m
(add 1) 2 -- returns 3

In the last line, we got a function from calling add 1 and then we called that function with the argument 2. As it turns out, function call is left associative, which means that whenever parenthesis are missing, the compiler places them from left to right.

That's confusing again, so we'll see some examples. The following are equivalent:

add 1 2
(add 1) 2

Maybe.map addOne (Just 2)
(Maybe.map addOne) (Just 2)

f a b c d e
((((f a) b) c) d) e

Appendix: Currying

In Javascript, when we have a function that takes several arguments we can convert them into a function that takes one argument and returns a function that takes another argument and returns a...

For example:

function addTakingTwoArguments(x, y) {
    return x + y;
}

addTakingTwoArguments(1, 2) // returns 3

function addTakingOneArgument(x) {
    return function(y) {
        return x + y;
    }
}

addTakingOneArgument(1)(2) // returns 3

The add function we wrote in elm is equivalent to addTakingOneArgument. The first function, addTakingTwoArguments, is impossible to write in elm, because all functions take only one argument!

The process of converting addTakingTwoArguments to addTakingOneArgument is called currying (named after Haskell Curry). One could say that all functions in elm are curried by default. In the elm community we usually don't use fancy names that might be confusing, that's why you probably won't hear about currying in the official guide, or in tutorials. I only mention it here because "currying" is a term common enough that you may have already heard it somewhere!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment