patternMinor
Similarities and differences between Unit and Bottom types?
Viewed 0 times
similaritiesbottomdifferencesbetweentypesandunit
Problem
I came across this recent Reddit thread, Thoughts on Botton vs Unit Types, but I don't understand what the similarities and differences are in regards to when you are creating a programming language.
If you are creating a programming language, why do you need both a Bottom and a Unit type (as defined in the thread).
For the sake of this discussion, let's call the bottom type a type that has no members. NIL in Common Lisp, Nothing in Scala, never in TypeScript, Never in Swift. This type can be used to indicate the return type of a function that never returns (a function that only throws an exception, for example).
Let's call the unit type a type that has a single member. NULL and NIL in Common Lisp (the symbol NIL not to be confused with the type NIL), () and () in Haskel, Unit and () in Scala, Void and () in Swift.
What exactly are the differences between the two as well?
In my PL, I currently just have a
If you are creating a programming language, why do you need both a Bottom and a Unit type (as defined in the thread).
For the sake of this discussion, let's call the bottom type a type that has no members. NIL in Common Lisp, Nothing in Scala, never in TypeScript, Never in Swift. This type can be used to indicate the return type of a function that never returns (a function that only throws an exception, for example).
Let's call the unit type a type that has a single member. NULL and NIL in Common Lisp (the symbol NIL not to be confused with the type NIL), () and () in Haskel, Unit and () in Scala, Void and () in Swift.
What exactly are the differences between the two as well?
In my PL, I currently just have a
void type, which is meant to mean "nothing" or "empty" or "null" I would think, but this throws into question what I am doing. It seems I need to add another type, but not sure how it fits in. Because then in Rust you have the None type, I don't see why I couldn't just make that the void type as well. That sort of stuff. What are the similarities and differences, and why do you need both?Solution
Jörg W Mittag gives an excellent answer. But I think it does miss the heart of the question. The quote says "For the sake of this discussion, let's call the bottom type a type that has no members." So while Jorg is right that the "the defining feature of a bottom type is not that it has no members" and that "it is perfectly possible for a bottom type to have members", we should also consider that it is perfectly possible for a bottom type to have no members.
Why would a type with no members be useful and why can't we just use the unit type for the same thing?
Unit and None
So let's consider a language that has a type
A case for
If we declare a function
where T is a type, that usually means that if a call returns, it returns a value that is a member of type T. Or to put it another way
Any call to foo either returns a member of T or doesn't return
If T is Unit, that means that any call to foo will return (if it returns) a member of Unit, i.e. unit. Since the value returned is entirely predictable, there is no point writing say
since the value of x is a forgone conclusion. We might as well just write
So what about None, why not use that instead of Unit for our analog of void?
Consider
From our understanding of what this means, we see that
Any call to bar either returns a member of None or doesn't return
But since None has no members, this is equivalent to
No call to bar returns.
That doesn't sound like void.
Three cases for
Is there any point to having
Here are three arguments for having a
A function that returns
This can be useful for defensive programming. E.g.
This should type-check since None is a subtype of Int.
What if we had used used Unit for the return type of
which looks rather arbitrary.
Here is a more compelling example. Here we have a generic function
For this to typecheck, we need for
The language implementation will (I'd hope) complain about the missing
since there is nothing we can write in the place of ....
Suppose our language has lists. (These are lists of values, as in Haskell -- not lists of locations as in Python.)
A reasonable rule for list catenation is that the type of
What is the type of the empty list constant
Intersection types
Let's introduce multiple inheritance into our language. Given two interfaces
Why would a type with no members be useful and why can't we just use the unit type for the same thing?
Unit and None
So let's consider a language that has a type
Unit with one member unit and a type None with no members. I'll assume that unit is not a member of "ordinary" types like Int. While we are at it, let's assume that the only members of Int are integers.A case for
UnitIf we declare a function
fun foo() : T is .... endwhere T is a type, that usually means that if a call returns, it returns a value that is a member of type T. Or to put it another way
Any call to foo either returns a member of T or doesn't return
If T is Unit, that means that any call to foo will return (if it returns) a member of Unit, i.e. unit. Since the value returned is entirely predictable, there is no point writing say
val x : Unit = foo()since the value of x is a forgone conclusion. We might as well just write
foo()Unit is playing the role that void plays in C or Java. The only difference is that we are treating Unit as a type, whereas in C and Java void is not really a type.So what about None, why not use that instead of Unit for our analog of void?
Consider
fun bar() : None is ... endFrom our understanding of what this means, we see that
Any call to bar either returns a member of None or doesn't return
But since None has no members, this is equivalent to
No call to bar returns.
That doesn't sound like void.
None is not a suitable analog for void, but Unit is.Three cases for
NoneIs there any point to having
None?Here are three arguments for having a
None type. But note that these arguments don't really rest on None having no members, but rather that it is a subtype of all other types in the language, i.e. that it is a bottom type.None as a return typeA function that returns
None sounds pretty useless. But it isn't completely useless. Suppose I have a function that always throws an exception, then I can writefun unreachable() : None is
throw new AssertionError("Unreacble code reached")
endThis can be useful for defensive programming. E.g.
fun partial(a : Int) : Int is
if a > 0
return 1
else if a < 0
return -1
else
return unreachable()
end
endThis should type-check since None is a subtype of Int.
What if we had used used Unit for the return type of
unreachable? The subroutine above would not type check. Since Unit is not a subtype of Int). So we'd have to rewrite the else part as, for exampleunreachable()
return 42which looks rather arbitrary.
Here is a more compelling example. Here we have a generic function
fun assertWithResult( b : Bool, v : T, m : String := "Assertion failed" ) : T is
if b
return v
else
return (throw new AssertionError( m ) )
end
endFor this to typecheck, we need for
throw new AssertionError( m ) to have the bottom type. If we rewrite the code aselse
throw new AssertionError( m )
endThe language implementation will (I'd hope) complain about the missing
return. We can't rewrite it aselse
throw new AssertionError( m )
return ...
endsince there is nothing we can write in the place of ....
null is not the answer because I'm supposing null is not a member of Int, for example. unit has the same problem. I'll consider the case where there is a null value that's in every type later.None as the type of something that isn't there.Suppose our language has lists. (These are lists of values, as in Haskell -- not lists of locations as in Python.)
A reasonable rule for list catenation is that the type of
xs ++ ys is List[T|U] where the type of xs is List[T], the type of ys is List[U] and T|U is the smallest type that contains the union of the members of T and U.What is the type of the empty list constant
[]. If it is List[Unit], then [1,2,3] ++ [] will have type List[Int|Unit], which is probably not what you want. But if the type of the empty list constant is List[None] we have List[Int|None] which is List[Int].Intersection types
Let's introduce multiple inheritance into our language. Given two interfaces
I and J we can write I&J for the combination of both. I.e. the members or I&J are exactly the members of I intersected with the members of J. Why not allow intersection types for any two types? Now what is Int&Unit? Again None comes Code Snippets
fun foo() : T is .... endval x : Unit = foo()fun bar() : None is ... endfun unreachable() : None is
throw new AssertionError("Unreacble code reached")
endfun partial(a : Int) : Int is
if a > 0
return 1
else if a < 0
return -1
else
return unreachable()
end
endContext
StackExchange Computer Science Q#151088, answer score: 7
Revisions (0)
No revisions yet.