简体   繁体   中英

What is the kind of Void?

Seeing as the type of Void is uninhabited, can it be regarded as a type "constructor"? Or is this just a quick "hack" to be able to safely disregard / disable functionality and am I looking too deep into this?

0 was once not considered to be a number. "How can nothing be something?" But over time we came to accept 0 as a number, noticing its properties and its usefulness. Today the idea that 0 is not a number is as absurd as the idea that it was one 2,000 years ago.

Void is a type the same way 0 is a number. Its kind is * , just like all other types. The similarity between Void and 0 runs quite deep, as Tikhon Jelvis's answer begins to show. There is a strong mathematical analogy between types and numbers, with Either playing the role of addition, tupling (,) playing the role of multiplication, functions (->) as exponentiation ( a -> b means b a ), () (pronounced "unit") as 1, and Void as 0.

The number of values a type may take is the numeric interpretation of the type. So

Either () (Either () ())

is interpreted as

1 + (1 + 1)

so we should expect three values. And indeed there are exactly three.

Left ()
Right (Left ())
Right (Right ())

Similarly,

(Either () (), Either () ())

is interpreted as

(1 + 1) * (1 + 1)

so we should expect four values. Can you list them?

Coming back to Void , you can have, say, Either () Void , which would be interpreted as 1 + 0. The constructors of this type are Left () , and Right v for every value v of type Void -- however there are no values of type Void , so the only constructor for Either () Void is Left () . And 1 + 0 = 1, so we got what we expected.

Exercise : What should the mathematical interpretation of Maybe a be? How many values of Maybe Void are there -- does this fit with the interpretation?

Notes

  • I am ignoring partiality in this treatment, pretending Haskell is total. Technically undefined can have type Void , but we like to use fast and loose reasoning which ignores these.
  • The way void is used in C-based languages is actually much more like Haskell's () than Haskell's Void . In Haskell, a function returning Void can never return at all, whereas in C languages a function returning void can return, but the return value is uninteresting -- there's only one thing it could be so why bother?

It's a type of kind * just like Int , Bool or () . It just happens to have 0 values instead of 1 or 2 or more.

It's not a hack but rather a fundamental part of Haskell's type system. It plays the role of 0 to () 's 1 and, if we look at types as propositions, Void corresponds to the proposition "false". It's also an identity to sum types ( Either ) just like () is an identity to product types: Either a Void is isomorphic to a .

In practice, it often acts as a dual of () ; the best example of this I've seen is in pipes where () is used to tag things that don't take inputs and Void (named X ) is used to tag things that don't produce outputs. (See Appendix:Types in the tutorial.)

It is a way to mark things as impossible or missing, but it is by no means a hack.

Another angle on this question: suppose I asked you to write a guaranteed-terminating function of type a -> b :

aintGonnaWork :: a -> b
aintGonnaWork a = _

As hopefully you can tell, it's impossible to write such a function. It follows from this that the type a -> b has no defined values. Note also that the kind of a -> b is * :

(->)   :: * -> * -> *
a      :: *
b      :: *
---------------------
a -> b :: *

And there we have it: a type of kind * , built from "vanilla" Haskell elements (no "hacks"), but which nevertheless has no defined values. So the existence of types like Void is already implicit in "vanilla" Haskell; all that the explicit Void type does is provide a standard, named one.

I'll close with a simple implementation of the Void type in terms of the above; the only extension necessary is RankNTypes .

{-# LANGUAGE RankNTypes #-}

newtype Void = Void (forall a b. a -> b)

absurd :: Void -> a
absurd (Void f) = f f

Luis Casillas showed that a polymorphic type can be uninhabited in vanilla Haskell. But there are also uninhabited monomorphic types. The classic one looks like this:

data Void = Void !Void

absurd :: Void -> a
absurd (Void x) = absurd x

Imagine trying to construct something of type Void .

void :: Void
void = Void _

You need something of type Void to fill the hole. Since that's the whole point of void , the only sensible choice is

void :: Void
void = Void void

If the Void constructor were lazy, that would be a cyclical structure Void (Void (Void ...)) . But since it's strict, void can be written equivalently as

void = void `seq` Void void

which is obviously not going to fly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM