Falling in love with Elm
I have fallen in love with Elm. I never saw myself as a functional programmer, but having tried a language that literally will not allow you to leave any stone unturned I am converted. So much so that I am seriously considering learning Haskell since it has web frameworks.
You see, as a lone developer, maintainability is paramount and functional languages are much more maintainable. But that is for another day. Today, I want to tell you what I have learned about functions and partial application in elm.
Partial Application
In Elm, an anonymous function is written like so:
\a -> a
This is the identity function. It simply returns whatever parameter (a
) is passed to it. If I called it as follows:
(\a -> a) "hello"
then it would return “hello” (realise that this is an anonymous function, which is why it looks a bit odd. Functions which are named by assigning them to a value have a more traditional syntax, i.e. id a = a
If I want to write an anonymous function that takes two arguments I would do it like this:
(\a -> (\b -> a + b))
This anonymous function takes two arguments, a
and b
and adds them together.
Now, let’s say that I write the following code in elm-repl
:
> f = (\a -> (\b -> a + b)) -- Add two numbers together
> f
<function> : number -> number -> number
> g = f 1 -- Partially apply the first argument
<function> : number -> number
> g 2
3 : number
So in passing only one of two arguments to f
we return a new function that has only one argument which assign to g
…or do we? Actually, we don’t create a new function. We unwrapped the inner function in f
and assign it g
. You see, f
is actually a function that returns another function. That’s why it has the syntax (arg -> (arg -> result))
. The parentheses represent the nesting of one function inside another. When you partially apply an argument to a function you create something that looks awfully like a closure*. g
knows the value of a
because it is defined in the environment that returned it, but this is invisible to the caller of g
.
Using <|
Another way of examining this can be through looking at the <|
operator. The <|
operator (called the Backward Function Applicator passes the value on its right to the first parameter of the function on its left:
> ((,) <| 1) 2
(1,2) : ( number, number' )
Note that partial application is not really the purpose of <|
. See the docs for an explanation (linked above).
Let’s break this down a little.
(,)
is shorthand for thetuple2
function which has the type\a -> (\b -> (a,b))
(,) <| 1
backward applies the number1
totuple2
returning the following function:a -> (number, a)
. Note that we have gone from(a,b)
to(number, b)
. So the value ofa
in the returned function has been filled in**. It now just needs its second argument to complete the computation.((,) <| 1) 2
passes2
to(a -> (number, a)
which returns(1,2)
.
So we see that <|
unwraps a nested function from an outer function by calling the outer function with the second argument given to |>
. The inner function is returned with the argument provided to the outer function assigned wherever it is going to be used by the inner function.
Aha!
The type annotation for <|
is: (a -> b) -> a -> b
. This provided the ‘aha’ moment for me about how partial application works. It says:
“I take two parameters: a function that accepts a
and returns b
, and an instance of a
. I will return an instance of b
.”
Now what is an instance of b
? Well, if you use <|
where (a -> b)
is a function that only takes one argument you get the result of calling (a -> b)
with an instance of a
, i.e.
> f = (\a -> a + 1)
<function> : number -> number
> f <| 1
2 : number
But if you use <|
where the first argument is a function that has two arguments then a partially applied function is returned! In other words the b
in (a -> b)
is the inner function in (\a -> (\b -> a + b))
or similar i.e.:
> f = (\a -> (\c -> a + c))
<function> : number -> number -> number
> f <| 1
<function> : number -> number -- this is (\c -> 1 + c)
Recall that the type annotation for <|
is (a -> b) -> a -> b
I hope this is helpful. Please feel to make any comments or suggest improvements below.
* I’m not saying it is a closure, since a quick Google search didn’t find a definitive answer to this point but if anyone happens to know I’d love to find out!
** This is why I suspect that it does not, in fact create a closure but something similar.
Written with StackEdit.
Comments
Post a Comment