Advanced Types in Elm - The Never Type
Part III: The Never Type
What is the Never type?
The Never type is a type that doesn’t have any values. It’s a type that can be specified in a type annotation, but you can’t construct a Never value because it is valueless.
- A Bool has two values:
- The unit type has one value:
- The Never type has no values
How can this be? This starts to make more sense if we look at how Never is defined in the source.
Let’s try and figure out how to use Never as a value. We’d need to use
JustOneMore and pass it some additional information.
Ok we need to fill in the blank. JustOneMore expects a Never. Easy.
Hmm. Never is recursively defined. This might take a while..
Hopefully it’s a little more clear that because Never is recursively defined it is impossible to use it as a value. Additionally, Never is an opaque type with no constructor functions exposed. It couldn’t be constructed outside of the Basics module even if it wasn’t recursively defined! There’s more info on opaque types in a previous post.
Wait a sec.. I’m still confused
Yea, I was too when I first started doing research for this post. How can there be an annotated type without a corresponding value?
The answer is simple. Not all types in a type annotation require a corresponding value in the function implementation.
The code above demonstrates that we see this sort of thing every day. In that example alwaysNothing is annotated as returning a
Maybe String, yet the body of the function doesn’t contain a string value. There’s an annotated type without a corresponding value.
Likewise, I could also update the annotation to return a
This function compiles just fine. The really neat part is if I change
Just "any type here" then I get a compiler error because Never doesn’t have any values. It forces this function to actually always return
Nothing by leveraging the type system!
Note that the docs for Never assert that “generally speaking, you do not want Never in your return types” so take this example with a grain of salt.
It seems obvious in hindsight, but honestly a lot of this didn’t click for me until I realized that Never will likely be used in conjunction with parameterized types like
When is it useful?
As is the case with extensible records, the Never type is useful for restricting function arguments.
The Task module does a good job of demonstrating this. There are two functions in that module that produce a
Cmd msg. The first function is
This function takes two pieces of information and returns a
Cmd. The two arguments include:
- A task that might fail
- A function that takes the
Resultfrom executing the task and turns it into a
This is all reasonable. But what about tasks that can never fail? For example, Time.now can’t fail. If a task cannot fail then we don’t need to handle the error case. In fact, there shouldn’t be any
Result to handle at all!
Well, that’s what
perform function operates on tasks that can never fail. Because a Task can be annotated with Never (the type of the error is Never) this function is guaranteed to never be called with a task that can fail. And since the task can never fail the first argument to the function can shrink from
(Result x a -> msg) to
(a -> msg). Never is restricting the function arguments.
If you’d like to play around with how the Task module uses Never I put together a small example with
perform, a task that can fail, and a task that will never fail.
Note that you can replace the usage of
taskWillNotFail, but not the other way around.
The Never type is a valueless type and like extensible records the Never type can be used to restrict function arguments, and that’s all there is to it!
Next up.. a look into phantom types..
Shoutout to the folks on the #beginners slack channel for answering my questions and adding clarification in a friendly way. You’re all incredibly helpful!