Advanced Elm

Type Parameter Design

Richard Feldman

Richard Feldman

Vendr, Inc.
Advanced Elm

Check out a free preview of the full Advanced Elm course

The "Type Parameter Design" Lesson is part of the full, Advanced Elm course featured in this preview video. Here's what you'd learn in this lesson:

Richard summarizes the tradeoffs between accepting Attribute Msg, Attribute msg, and Attribute Never. Phantom types and non-phantom types are discussed, as well as how to use Never as a constraint to require that something is still unbound, and how that will unify with two different type variables.


Transcript from the "Type Parameter Design" Lesson

>> Richard Feldman: So one other use for, sorry, so that's essentially the trade offs between these three. So the first one lists attribute message with a lowercase m. It says I will accept any type of attribute you want to give me, I don't care what it is, just throw it at me and I will accept it.

Second one says, give me an attribute that is specifically your message type. Like this concrete, I want this exact type of message. And the third one says, only give me a list of unbound attributes. They have to still be unbound when you provide them to me, otherwise I will not accept them.

They can't be doing any event handling on them. So those are the trade offs, and there are good reasons to use all three of these. Okay, so the reason that it's called never, incidentally as we saw anyone can define their own never type. They don't have to call it never, they can call it whatever they want.

The whole point of it is just to define it in terms of itself so that it can't be instantiated. Test is a reused one that comes with the core package, and its purpose in that package is with tasks. And it's called never because the idea is it represents a task that never fails, a task that cannot possibly fail.

And it's used in this function, Task.perform. So tasks in Elm, if you've never used them before, are essentially the idea of sort of a chainable command is one way of thinking of it. So it's gonna perform an effect of some sort, a task might include things like running an HTP request, you can convert your HTP requests to tasks.

You can chain them together and say when this task is done, run this next task assuming the previous one succeeded, things like that. And by default tasks much like result have both an error type, as well as success type. So you can say like I have a task for example of http.error as well as whatever the success value of your HTTP request is based on what it's decoding.

And what Task.perform is saying right here is first, it's saying, okay, I need a way to translate your success type into a message. And I need you to give me a task that never fails. I need you to give me a task that still has its error type unbound because this task is not capable of producing errors.

So its exactly the same principle that the test was using to say, I want a list of attributes that do not produce messages. This is saying I want a task that does not produce errors. And if so then I can give you a command that will work. So essentially this is saying, give me a way you can convert success type into a message, and then I'll take your task, which I know can't fail, I'll run it, I know it's gonna succeed because it can't possibly fail.

It'll give me that success value, I'll convert that into a message, and then there's your command. This is in contrast with Task.attempt, which requires two of these functions right here, one for the success case and one for the failure case. Perform says, you don't need to specify what to do in the failure case, because by this type I know that the failure case can't come up.

So that's an example, here is a task that can never fail. This is called getViewport and it returns the dimensions of the entire window present, which of course can change, that the user can resize the window. So depending on when you call this it might give you a different answer which is why the task is opposed to just a plain function that just returned the viewport.

So getting the viewport is a task that can never fail. It will always return an answer. And that's represented by the fact that its error type is still unbound in the function. Sorry, it's not a function, it's just a value. So here's the type of getViewport again, remember for the next slide that we're accepting a Task Never a.

So let's say, our message type says, we have one variant called ViewportChanged, and it accepts a Viewport. We're gonna call Task.perform passing ViewportChanged which was the aid to message function which is to say, this variant right here. It has getViewport as the other argument, which is Task x Viewport.

Now remember that, that second argument there was expecting a Task Never a, so a is the success type which is accurate for what we want here. And then basically Elm is going to unify these two and say okay great, this is going to give me back. So it's expecting a task that never fails which is to say Task Never a and what we're giving it is a Task x Viewport.

So it's actually going to unify both of those at the same time, in the same way that we saw with Open Records previously. So the first type variable, it's gonna say, okay, Never and unbound, that unifies to Never, because Never is the more constrained of the two. And then for the success type it's gonna unify unbound a with Viewport and unify those the more constrained Viewport.

So in the end, it says, well, we have a Task Never Viewport. Fantastic that works, that does what we want. And so I will compile and convert it to command and everything works out great. Yeah?
>> Speaker 2: I'm assuming the reason that getViewport, the error type isn't actually Never is that it can be used for other tasks that are maybe less-

>> Richard Feldman: Yes, exactly, yeah. So the reason that this is not Never, but rather an unbound type is yeah, exactly, so that it is compatible with any other tasks. So if you wanna chain this together with other tasks, it's really important that it be compatible with them. If we're gonna chain two tasks together, they have to have the same error type, or at least compatible error types.

So that if something fails, Elm knows what the error type is that it's gonna send to the message that represents that failure. So this can be chained together with absolutely anything, because there's no chance that it's gonna fail. So just use whatever error type the other thing has, but that wouldn't be true if it were Task Never Viewport.

Are there questions? Okay, so to recap, we talked about units of measure and how we can prevent expensive space equipment from crashing, by using custom types with type variables. We talked about accessible HTML and sort of ways that we can implement that and prevent ourselves from constructing paragraph tags that have click handlers on them by using never.

We talked about the other uses for the never type specifically in tasks, the idea of a task that can never fail. And finally, we talked about type parameter design in the context of the trade offs between phantom types and non-phantom types. We talked about how we can use Never as a constraint to require that something is still unbound.

And how that will unify with when you have two different type variables as we saw in the case of Task.perform.

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