I just got finished reading the paper Levity Polymorphism.
I had a question about why undefined
can be levity-polymorphic when used as an unboxed type.
First, let's start with some definitions of boxity from the paper:
boxed:
A boxed value is represented by a pointer into the heap.
Int
andBool
are examples of types that have boxed values.
unboxed:
The paper follows with some definitions of levity:
lifted:
A lifted type is one that is lazy.
A lifted type has on extra element beyond the normal ones, representing a non-terminating computation.
For example, the type
Bool
is lifted, meaning that there are three different values forBool
:True
,False
, and⊥
(bottom).All lifted types MUST be boxed.
unlifted
An unlifted type is one that is strict.
The element
⊥
does not exist in an unlifted type.Int#
andChar#
are examples of unlifted types.
The paper goes on to explain how GHC 8 provides functionality allowing type variables to have polymorphic levity.
This allows you to write a levity-polymorphic undefined
with the following type:
undefined :: forall (r :: RuntimeRep). forall (a :: TYPE r). a
This says that undefined
should work for any RuntimeRep
, even unlifted types.
Here is an example of using undefined
as an unboxed, unlifted Int#
in GHCi:
> :set -XMagicHash
> import GHC.Prim
> import GHC.Exts
> I# (undefined :: Int#)
*** Exception: Prelude.undefined
I've always thought of undefined
as being the same as ⊥
(bottom). However, the paper says, "The element ⊥
does not exist in an unlifted type."
What is going on here? Is undefined
not actually ⊥
when used as an unlifted type? Am I misunderstanding something about the paper (or boxity or levity)?
⊥
is any computation that cannot terminate normally, including e.g. infinite loops. OTOHundefined
is a known value which is checked for at runtime. – 9000undefined
throws an exception, and is bottom in that way. – Daniel Wagner_
) can never be bound to a bottom value of an unlifted type. Givenf (x :: Int#) = ...
, if I callf undefined
, execution will never reach the right-hand side. GHC will attempt to evaluateundefined
before callingf
. – dfeuerundefined
with the signature you indicate. ItsHasCallStack
constraint is necessary to satisfy the type checker. That makes it (effectively) a function rather than a value. – dfeuer