A bottom type is a construct primarily appearing in mathematical type theory. It is also called the empty type. It is a type that has no values, but is a subtype of all types.
If a function’s return type is the bottom type, that means that it does not return. Period. Maybe it loops forever, or maybe it throws an exception.
What is the point of having this weird type in a programming language? It’s not that common, but it is present in some, such as Scala and Lisp.
14
I’ll take a simple example: C++ vs Rust.
Here is a function used to throw an exception in C++11:
[[noreturn]] void ThrowException(char const* message,
char const* file,
int line,
char const* function);
And here is the equivalent in Rust:
fn formatted_panic(message: &str, file: &str, line: isize, function: &str) -> !;
On a purely syntactic matter, the Rust construct is more sensible. Note that the C++ construct specifies a return type even though it also specifies it is not going to return. That’s a bit weird.
On a standard note, the C++ syntax only appeared with C++11 (it was tacked on top), but various compilers had been providing various extensions for a while, so that third party analysis tools had to be programmed to recognize the various ways this attribute could be written. Having it standardized is obviously clearly superior.
Now, as for the benefit?
The fact that a function does not return can be useful for:
- optimization: one can prune any code after it (it won’t return), there is no need to save the registers (as it won’t be necessary to restore them), …
- static analysis: it eliminates a number of potential execution paths
- maintainability: (see static analysis, but by humans)
10
Karl’s answer is good. Here is an additional use that I don’t think anyone else has mentioned. The type of
if E then A else B
should be a type that includes all the values in the type of A
and all the values in the type of B
. If the type of B
is Nothing
, then the type of the if
expression can be the type of A
. I’ll often declare a routine
def unreachable( s:String ) : Nothing = throw new AssertionError("Unreachable "+s)
to say that code is not expected to be reached. Since its type is Nothing
, unreachable(s)
can now be used in any if
or (more often) switch
without affecting the type of result. For example
val colour : Colour := switch state of
BLACK_TO_MOVE: BLACK
WHITE_TO_MOVE: WHITE
default: unreachable("Bad state")
Scala has such a Nothing type.
Another use case for Nothing
(as mentioned in Karl’s answer) is List[Nothing] is the type of lists each of whose members has type Nothing. Thus it can be the type of the empty list.
The key property of Nothing
that makes these use cases work is not that it has no values –although in Scala, for example, it does have no values– it is that it is a subtype of every other type.
Suppose you have a language where every type contains the same value — let’s call it ()
. In such a language the unit type, which has ()
as its only value, could be a subtype of every type. That doesn’t make it a bottom type in the sense that the OP meant; the OP was clear that a bottom type contains no values. However, as it is a type that is a subtype of every type, it can play much the same role as a bottom type.
Haskell does things a bit differently. In Haskell, an expression that never produces a value can have the type scheme forall a.a
. An instance of this type scheme will unify with any other type, so it effectively acts as a bottom type, even though (standard) Haskell has no notion of subtyping. For example, the error
function from the standard prelude has type scheme forall a. [Char] -> a
. So you can write
if E then A else error ""
and the type of the expression will be the same as the type of A
, for any expression A
.
The empty list in Haskell has the type scheme forall a. [a]
. If A
is an expression whose type is a list type, then
if E then A else []
is an expression with the same type as A
.
2
Types form a monoid in two ways, together making a semiring. That’s what’s called algebraic data types. For finite types, this semiring directly relates to the semiring of natural numbers (including zero), which means you count how many possible values the type has (excluding “nonterminating values”).
- The bottom type (I’ll call it
Vacuous
) has zero values†. - The unit type has one value. I’ll call both the type and its single value
()
. - Composition (which most programming languages support quite directly, through records / structs / classes with public fields) is a product operation. For instance,
(Bool, Bool)
has four possible values, namely(False,False)
,(False,True)
,(True,False)
and(True,True)
.
The unit type is the identity element of the composition operation. E.g.((), False)
and((), True)
are the only values of type((), Bool)
, so this type is isomorphic toBool
itself. - Alternative types are somewhat neglected in most languages (OO languages kind-of support them with inheritance), but they are no less useful. An alternative between two types
A
andB
basically has all the values ofA
, plus all the values ofB
, hence sum type. For instance,Either () Bool
has three values, I’ll call themLeft ()
,Right False
andRight True
.
The bottom type is the identity element of the sum:Either Vacuous A
has only values of the formRight a
, becauseLeft ...
doesn’t make sense (Vacuous
has no values).
What’s interesting about these monoids is that, when you introduce functions to your language, the category of these types with the functions as morphisms is a monoidal category. Amongst other things, this allows you to define applicative functors and monads, which turn out to be an excellent abstraction for general computations (possibly involving side-effects etc.) within otherwise purely functional terms.
Now, actually you can get quite far with worrying only one side of the issue (the composition monoid), then you don’t really need the bottom type explicitly. For instance, even Haskell did for a long time not have a standard bottom type. Now it has, it’s called Void
.
But when you consider the full picture, as a bicartesian closed category, then the type system is actually equivalent to the whole lambda calculus, so basically you have the perfect abstraction over everything possible in a Turing-complete language. Great for embedded domain-specific languages, for instance there’s a project about directly coding electronic circuits this way.
Of course, you may well say that this is all theoretists’ general nonsense. You don’t need to know about category theory at all to be a good programmer, but when you do, it gives you powerful and ridiculously general ways to reason about code, and proove invariants.
†mb21 reminds me to note that this should not be confused with bottom values. In lazy languages like Haskell, every type contains a bottom “value”, denoted ⊥
. This isn’t a concrete thing that you could ever explicitly pass around, instead it’s what’s “returned” for example when a function loops forever. Even Haskell’s Void
type “contains” the bottom value, thus the name. In that light, Haskell’s bottom type really has one value and its unit type has two values, but in category-theory discussion this is generally ignored.
1
Maybe it loops forever, or maybe it throws an exception.
Sounds like a useful type to have in those situations, rare though they may be.
Also, even though Nothing
(Scala’s name for the bottom type) can have no values, List[Nothing]
does not have that restriction, which makes it useful as the type of an empty list. Most languages get around this by making an empty list of strings a different type than an empty list of integers, which kind of makes sense, but makes an empty list more verbose to write, which is a big drawback in a list-oriented language.
6
It is useful for static analysis to document the fact that a particular code path is not reachable. For example if you write the following in C#:
int F(int arg) {
if (arg != 0)
return arg + 1; //some computation
else
Assert(false); //this throws but the compiler does not know that
}
void Assert(bool cond) { if (!cond) throw ...; }
The compiler will complain that F
does not return anything in at least one code path. If Assert
were to be marked as non-returning the compiler would not need to warn.
In some languages, null
has the bottom type, since the subtype of all types nicely defines what languages use null for (despite the mild contradiction of having null
be both itself and a function that returns itself, avoiding the common arguments about why bot
should be uninhabited).
It can also be used as a catch-all in function types (any -> bot
) to handle dispatch gone awry.
And some languages allow you to actually resolve bot
as an error, which can be used to provide custom compiler errors.
9
Yes this is a quite useful type; while its role would be mostly interior to the type system, there are some occasion where the bottom type would appear in openly.
Consider a statically typed language in which conditionals are expressions (so the if-then-else construction doubles as the ternary operator of C and friends, and there might be a similar multi-way case statement). Functional programming language have this, but it happens in certain imperative languages as well (ever since ALGOL 60). Then all branch expressions must ultimately produce the type of the whole conditional expression. One could simply require their types to be equal (and I think this is the case for the ternary operator in C) but this is overly restrictive especially when the conditional can also be used as conditional statement (not returning any useful value). In general one wants each branch expression to be (implicitly) convertible to a common type that will be the type of the full expression (possibly with more or less complicated restrictions to allow that common type to be effectively found by the complier, cf. C++, but I won’t go into those details here).
There are two kinds of situations where a general kind of conversion will allow necessary flexibility of such conditional expressions. One is already mentioned, where the result type is the unit type void
; this is naturally a super-type of all other types, and allowing any type to be (trivially) converted to it makes it possible to use the conditional expression as conditional statement. The other involves cases where the expression does return a useful value, but one or more branches are incapable of producing one. They will usually raise an exception or involve a jump, and requiring them to (also) produce a value of the type of the whole expression (from an unreachable point) would be pointless. It is this kind of situation that can be gracefully handled by giving exception-raising clauses, jumps, and calls that will have such an effect, the bottom type, the one type that can be (trivially) converted into any other type.
I would suggest writing such a bottom type as *
to suggest its convertibility to arbitrary type. It may serve other useful purposes internally, for instance when trying to deduce a result type for a recursive function that does not declare any, the type inferencer could assign the type *
to any recursive call to avoid a chicken-and-egg situation; the actual type will be determined by non-recursive branches, and the recursive ones will be converted to the common type of the non-recursive ones. If there are no non-recursive branches at all, the type will remain *
, and correctly indicate that the function has no possible way of ever returning from the recursion. Other than this and as result type of exception throwing functions, one can use *
as component type of sequences of length 0, for instance of the empty list; again if ever an element is selected from an expression of type [*]
(necessarily empty list), then the resulting type *
will correctly indicated that this can never return without an error.
7
In some languages, you can annotate a function to tell both the compiler and developers that a call to this function isn’t going to return (and if the function is written in a way that it can return, the compiler won’t allow it). That’s a useful thing to know, but in the end you can call a function like this like any other. The compiler can use the information for optimisation, to give warnings about dead code, and so on. So there is no very compelling reason to have this type, but no very compelling reason to avoid it either.
In many languages, a function can return “void”. What that exactly means depends on the language. In C it means the function returns nothing. In Swift, it means the function returns an object with only one possible value, and since there is only one possible value that value takes zero bits and doesn’t actually require any code. In either case, that’s not the same as “bottom”.
“bottom” would be a type with no possible values. It can never exist. If a function returns “bottom”, it cannot actually return, because there is no value of type “bottom” that it could return.
If a language designer feels like it, then there is no reason to not have that type. The implementation is not difficult (you can implement it exactly like a function returning void and marked as “doesn’t return”). You can’t mix pointers to functions returning bottom with pointers to functions returning void, because they are not the same type).