Phantom Types
See original GitHub issuePhantom types are a proposal to add types that have no runtime values. Such types are useful in several scenarios:
- Following Curry-Howard, we can model propositions as types and terms as their proofs. If a proposition is represented by a phantom type, the proofs corresponding to it are erased at runtime.
- We can model capabilities as unforgeable values of types. If the types are phantom types, the capabilities are purely static, they need not be passed at runtime.
Phantom types have a prototype implementation #1408. This implementation has evolved quite a bit over time. The present issue describes the latest state of the proposal which arose from discussions between @nicolasstucki, myself and other members at LAMP. It is still vague in several places and there are some issues left open.
The proposal introduces a new predefined class scala.Phantom
, defined as follows
package scala
class Phantom {
sealed abstract trait Any
final abstract trait Nothing extends Any
protected def assume[T <: Any]: T
class Function1[-T <: Any, +R <: Any] {
def apply(x: T): R
}
class ImplicitFunction1[-T <: Any, +R <: Any] extends Function1[T, R] {
def apply(implicit x: T): R
}
... same for other function arities ...
}
This class is not given in source but has to be synthesized in the compiler. Phantom
is special in that Phantom.Any
and Phantom.Nothing
live completely outside the standard type hierarchy. They are not subtypes of scala.Any
nor supertypes of scala.Nothing
. A type is called phantom type if it extends p.Any
where p
is an instance of class Phantom
.
Definition: All types that are supertypes of scala.Nothing
and subtypes of scala.Any
make up the standard type domain. If p
is an instance of class Phantom
, then all types that are supertypes of p.Nothing
and subtypes of p.Any
make up the phantom type domain of p
.
There are some rules to observe:
- Type bounds cannot be mixed between different type domains.
- Intersections (
&
) and unions (|
) cannot be formed between different type domains.
Since p.Any
is sealed, it is impossible to define a class that extends it. Since it is abstract, one cannot create instances of it. Likewise, one can neither instantiate nor extend p.Nothing
. It follows that no new classes can be defined in a phantom domain, and no objects of phantom types can be created using new
. However, one can define new phantom types as abstract types with phantom type bounds or as aliases of phantom types.
Every type parameter and abstract type has (possibly implied) bounds. These can be either phantom or non-phantom types. It follows that it is not possible to have a generic type parameter that ranges over both phantom and non-phantom types, nor can such a parameter range over phantom types from different domains.
If p
is an instance of Phantom
, the expression p.assume[T]
synthesizes a value of phantom type T
. This is the only way to produce a value of a phantom type. The actual implementation of assume
is irrelevant because all phantom types are erased. That is,
- All value definitions or parameters of phantom types are deleted.
- All method definitions returning phantom types are deleted. It should be checked that such methods are pure, i.e. they do not have observable side effects.
- All expressions of phantom types are deleted. It’s an open question whether expressions of phantom types must be pure; if they are not, the side-effecting parts of such expressions have to be lifted out and executed.
We discussed a lot about the nature of assume
. If phantom types model a theory then assume
defines axioms of that theory. If they define capabilities, then assume
defines capabilities given a priori. Either way, adding assume
s in arbitrary places destroys the soundness of the theory or the safety guarantees of the capability system. So assume
needs to be carefully controlled.
Note that assume
is protected
in class Phantom
, This means that individual phantom domains can each implement their own policy to what degree assume
should be made available to clients. A phantom domain could either hide assume
completely, and only export some fixed axioms or capabilities that are defined in terms of it. Or it could export assume
- either unchanged or under a different name such as uncheckedAssume
.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1
- Comments:14 (11 by maintainers)
Top GitHub Comments
One other question is whether there should be just one phantom domain or several. If we want to integrate perishable capabilities (aka effects) we might need another phantom domain for them where we are not allowed to capture values of such types at all. It could also be useful for other purposes to have several completely isolated phantom domains. A simple way to achieve that would be to change the object definition of
Phantom
to a class which can be inherited.@joan38 TL;DR IIUC: With unused parameters you can mark values of arbitrary types to be erased at runtime, instead of having to implement separate phantom types. I don’t have a link though yet.