Check out a free preview of the full Introduction to Elm, v2 course

The "Functions" Lesson is part of the full, Introduction to Elm, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Richard uses the Elm REPL to explain why type annotations for functions benefit directly from partial application in Elm.


Transcript from the "Functions" Lesson

>> Richard Feldman: All right, if this is our message type and we've seen that that's the type of message and then we've seen that model also we've given a type alias. What's the type of update? This is the type of update, update takes a message and a model and returns a model.

Now if you're like me, the first time you saw a type annotation like this you thought, that looks weird. That doesn't look like that's what it's saying, it looks like it's saying it takes a message and then returns a model and then returns another model, what's going on there?

Why are there two arrows there, shouldn't there be something like message comma model? So I thought this was very weird until someone explained to me why it is that way and then afterwards I was like, that's really cool. Not only do I understand this now, but I actually think this is how it should be and any other way would be weird and a misrepresentation of what's actually accurate.

So I'm gonna explain what was explained to me and we'll see if you have the same reaction. Okay, so Elm repl is a nice tool for messing around with things and sort of playing around with stuff. It's a read eval print loop. And if you've never used a repl before, the basic idea is you put in an expression such as, let's say, I wanna call our pluralized function, passing leaf, leaves, and 3, and it just prints out the result.

So in this case, 3 leaves. Also in Elm repl there's a nice little feature where it will tell you the type that it returned. So because Elm does type inference, it's like, hey I know the answer to that. So I'm just gonna let you know not only what the value is but also what that value type is right there in the repl.

So if I called pluralize leaf, leaves, and 3, comes back with three leaves as the answer. I can also just put a plain function in there and Elm repl will just say, well here's the tag for that function. So in this case it takes a string and another string and then an int and returns a string.

And there's that syntax at the end with all the arrows. Okay, now here's an interesting question, what happens if I put in pluralize leaf and leaves? Well, if you remember from earlier on the workshop, we discussed spatial application. So when you call an Elm function and you leave off an argument, Elm says, you're not done calling this function yet.

So I'm not gonna execute it, I'm not gonna pass undefined or anything like that cuz we don't have undefined. What I'm gonna do is I'm gonna say, okay, you gave me this function, you gave me two out of the three arguments. What I'm gonna give you back is a function that takes the last argument and then, finally, finishes the job and returns the thing that the overall function returns.

So that's partial application, okay, so the first thing makes sense to us. Obviously, if you pass all the arguments, it gives us back a string. Also the last thing, I think, more intuitive than the middle thing in the sense that if I call it passing two out of three arguments.

So it's gonna give me back a function that clearly takes an int which is the final argument and then returns the string that is the result of finishing the job. How do we square those with that middle thing, where the multiple arguments have the multiple arrows. Okay, lets think about what we know or what we've observed in a little bit of a different way.

So we're calling pluralize passing leaf, leaves, and 3, and that gives us back a string. And we've also established that if you call pluralize passing just leaf and leaves, and leaving off the 3. That gives us back a function that takes an int and returns a string, because of partial application.

Now, another way we could write this out, is we could put parentheses around this, that's fine, right? I mean, parentheses are essentially harmless in this case, we're just saying wanna put parentheses around that expression. That's legit. Another way we could do this is we could put the parentheses down there and also add a three on the end.

So this is essentially we've taken the previous one and said, well whats in those parentheses is a function from into string. So let's take that function and give it an int. Well, sure enough that will give us a string. And actually if we look at this, we can see the bottom one and the top one are basically the same.

I mean, all we've done is we've just added some parenthesis. So the first and third example here are really the exact same thing. It's just in both cases we are taking pluralize leaf and leaves and then giving it a three. We get the same answer either way, with or without the parenthesis.

Okay, so let's just get rid one of them so we can just kinda see things in this way. All right, what happens if we get it only one of the three arguments. Well, the way the partial application works is that, anytime you partially apply a function, however remaining arguments you leave off, that's how many arguments the resulting function is gonna take.

So if we call pluralize just passing leaf, it's gonna give us back a function which takes a string, and then returns a function that takes an int and returns a string. So we can see that in action by adding some more parentheses down here, and say okay, what happens if I call that thing in this way?

Well, if I take this, it's a function that takes the string and returns a function that returns, takes an int and returns the string, is that true? Well yeah, we can see that from the example down below now that I've added additional parenthesis. If I take this exact function, and I give it a string like it asked for, well, sure enough, what I get back is a function that takes an int and returns a string.

And if I take that, and all those parentheses, and I give it an int, well then sure enough I get a string. So we can start to see a pattern develop here. Each time, we partially apply it one more. If we give it one more argument, it gives us back sort of the next thing in the triangle here.

Take that one step further and not even partially apply it all. Let's just give it zero arguments. Well, to continue the pattern, now we're like okay, well, give me that very first string and I will give you back the next thing which is to say, a function that takes an int and returns a string.

So now we sort of like built it all the way up and as we can see in each of these cases when we give it one more argument. All we're doing is partially applying it one more time and getting one more specific function until eventually we run out of functions, and we're left with just a string.

And what if I told you that if when I just have pluralize by itself, you just don't need those parenthesis and the type annotation. So the reason that we annotate it like this is essentially that either of these is perfectly valid. And in fact, if you want to annotate your types like this Elm will allow it because it's accurate.

It's just that this looks nicer, so people tend to do it without the parentheses and they mean the same thing. So that's why this is sort of the canonical way of writing this, but it's actually also really the only accurate way to write it. Because this actually is what is going on.

It really is true that pluralize a function that takes the string and returns a function that takes the integer and return a string. It's just that the way that Elm calls it, it's sort of masking that by making it look as if it's actually a function that requires three arguments and takes three arguments.

But when we start to look at what happens when you partially apply it, we can kind of see the truth, which is that really, essentially, every Elm function really only takes one argument. It's just that quite a lot of Elm function happen to take argument, then return another function which possible takes one argument and returns another function.

But the syntax makes it nice, so that you have the ergonomics of calling with multiple arguments while still having this nice guarantee under the hood that partial application just works consistently across the board. In summary, that's why updates type as this, [LAUGH] message, a model, and a model.

Okay, to sum up, we talked about type annotations, so we talked about record annotations, primitive annotations. We can see int, Bool and point which is a record that has both. We talked about type parameters, parameterized types, we talked about List String which is to say list of strings.

And we talked about HTML message, which is say some HTML that emits a message of that particular type when the user interacts and sends that type of message to update. We talked about how it's really important that those agree. And if HTML's parameter is on a different type than what update expects, then it won't compile, because those need to be consistent.

We talked about type aliases which is a way to give a name to a particular type. So that we can break our types down into different named things rather than having to glob them all together when we have complex records. And finally, we talked about how to annotate function, which is to say, annotate them based on partial application.

If they have multiple arguments, then we're going to represent that honestly by showing an arrow at each case. Because essentially every Elm function really only has one argument, it's just that sometimes it returns an additional function.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now