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 + 1That 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 + 1Remember 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 + 1We can annotate the type for the function addOne as:
addOne : Int -> IntThis 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 + mThis function can also be written:
add = \n m -> n + m
add n m = n + mHow 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 -> IntWe 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 3All 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 3In 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) eAppendix: 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 3The 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!