I\'m curious about the \'undefined\' value in Haskell. Its interesting because you can put it just about anywhere, and Haskell will be happy. The following are all a-ok
How does undefined
work? Well, the best answer, IMHO, is that it doesn't work. But to understand that answer, we have to work through its consequences, which are not obvious to a newcomer.
Basically, if we have undefined :: a
, what that means for the type system is that undefined
can appear anywhere. Why? Because in Haskell, whenever you see an expression that has some type, you can specialize the type by consistently substituting all instances of any of its type variables for any other type. Familiar examples would be things like this:
map :: (a -> b) -> [a] -> [b]
-- Substitute b := b -> x
map :: (a -> b -> c) -> [a] -> [b -> c]
-- Substitute a := Int
map :: (Int -> b -> c) -> [Int] -> [b -> c]
-- etc.
In the case of map
, how does this work? Well, it comes down to the fact that map
's arguments provide everything that's necessary to produce an answer, no matter what substitutions and specializations we make for its type variables. If you have a list and a function that consumes values of the same type as the list's elements, you can do what map does, period.
But in the case of undefined :: a
, what this signature would mean is that no matter what type a
may get specialized to, undefined
is able to produce a value of that type. How can it do it? Well, actually, it can't, so if a program actually reaches a step where the value of undefined
is needed, there is no way to continue. The only thing the program can do at that point is to fail
The story behind this other case is similar but different:
loop :: a
loop = loop
Here, we can prove that loop
has type a
by this crazy-sounding argument: suppose that loop
has type a
. It needs to produce a value of type a
. How can it do it? Easy, it just calls loop
. Presto!
That sounds crazy, right? Well, the thing is that it's really no different from what's going on in the second equation of this definition of map
:
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
In that second equation, f x
has type b
, and (f x:)
has type [b] -> [b]
; now to conclude our proof that map
indeed has the type our signature claims, we need to produce a [b]
. So how are we doing it? By assuming that map
has the type we're trying to prove it has!
The way Haskell's type inference algorithm works is that it first guesses that the expression's type is a
, and then it only changes its guess when it finds something that contradicts that assumption. undefined
typechecks to a
because it's a flat-out lie. loop
typechecks to a
because recursion is allowed, and all loop
does is recurse.
EDIT: What the heck, I might as well spell out one example. Here's an informal demonstration of how to infer the type of map
from this definition:
map f [] = []
map f (x:xs) = f x : map f xs
It goes like this:
map :: a
.a
can't be the type. We revise our assumption to this: map :: a -> b -> c; f :: a
.map :: a -> [b] -> c; f :: a
.map :: a -> [b] -> [c]; f :: a
.(:) :: b -> [b] -> [b]
. This means that in that equation, x :: b
and xs :: [b]
.map f (x:xs)
must be of type [c]
, that means f x : map f xs
must also be of type [c]
.(:) :: c -> [c] -> [c]
, that means that f x :: c
and map f xs :: [c]
.map f xs :: [c]
. We had assumed that in (6), and if we had concluded otherwise in (7) this would have been a type error. We can also now dive into this expression and see what types this requires f
and xs
to have, but to make a longer story short, everything's going to check out.f x :: c
and x :: b
, we must conclude that f :: b -> c
. So now we get map :: (b -> c) -> [b] -> [c]
.The same process, but for loop = loop
:
loop :: a
.loop
takes no arguments, so its type is consistent with a
so far.loop
is loop
, which we've provisionally assigned type a
, so that checks out.loop
has type a
.