Skip to content

Commit f0eeea3

Browse files
biboudisnicolasstucki
authored andcommitted
Update phantom-types.md
Some edits to make the content clear! Good!
1 parent 45b3c6b commit f0eeea3

File tree

1 file changed

+41
-44
lines changed

1 file changed

+41
-44
lines changed

docs/docs/reference/phantom-types.md

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,21 @@ title: "Phantom Types"
77
What is a phantom type?
88
-----------------------
99

10-
A phantom type is a manifestation of abstract type that has no effect on the runtime.
11-
These are useful to prove static properties of the code using type evidences.
12-
As they have no effect on the runtime they can be erased from the resulting code by
13-
the compiler once it has shown the constraints hold.
14-
15-
When saying that a they have no effect on the runtime we do not only mean side effects
16-
like IO, field mutation, exceptions and so on. We also imply that if a function receives
17-
a phantom its result will not be affected by this argument.
18-
19-
As phantom do not live at runtime they cannot be subtypes of `scala.Any`, which deffines
20-
methods such as `hashCode`, `equals`, `getClass`, `asInstanceOf` and `isInstanceOf`.
21-
All these operations cannot exist on phantoms as there will not be an underlying object
22-
instance at runtime. At first glace this could look like a limitation, but in fact not
23-
having `asInstanceOf` will make constraints more reliable as it will not be possible to
24-
downcast a phantom value to fake an evidence.
25-
26-
If they don't live in the universe bounded by `scala.Any` and `scala.Nothing` where do
27-
they live? The answer is in their own type universes bounded by their phantom `Any` and `Nothing`.
10+
A phantom type is a manifestation of an abstract type that has no effect on the runtime execution of the program.
11+
Phantom types are useful to prove static properties of the code, using evidence or witnesses that the aforementioned properties hold at the type level.
12+
The most significant property of phantom types is that they have no _effect_ on the runtime since they can be erased from the resulting code by the compiler once it has shown that the constraints hold.
13+
14+
When saying that they have no effect on the runtime we not only mean side effects
15+
such as IO, field mutation, exceptions but also that the result of a function receiving a phantom will not be affected by this argument.
16+
17+
Phantoms are not represented by an object instance at runtime.
18+
Subsequently, since phantoms do not live at runtime, they cannot be subtypes of `scala.Any`, which defines methods such as `hashCode`, `equals`, `getClass`, `asInstanceOf` and `isInstanceOf`.
19+
At first glace this could look like a limitation.
20+
However, by not having `asInstanceOf` makes constraints more reliable as it will not be possible to downcast a phantom value to fake an evidence.
21+
22+
An interesting question is the following: if they don't live in the _type universe_, bounded by `scala.Any` and `scala.Nothing`, where do they live? The answer is in their own type universes bounded by their phantom `Any` and `Nothing`.
2823
In fact we allow multiple phantom universes to exist.
24+
In the following figure we demonstrate three such universes.
2925

3026
```none
3127
+-----+ +---------------+ +--------------------+
@@ -49,15 +45,16 @@ In fact we allow multiple phantom universes to exist.
4945
+---------+ +-------------------+ +------------------------+
5046
```
5147

52-
Inside a universe it types support the full Dotty type system. But we cannot mix types from
53-
different universes with `&`, `|` or in type bounds. Each type must be fully defined one universe.
54-
48+
Inside a universe types support the full Dotty type system.
49+
However, we cannot mix types from different universes with `&`, `|` or in type bounds.
50+
Each type must be fully defined one universe.
5551

5652
Implement your own phantom type
5753
-------------------------------
5854
Phantom types are definded by an `object` extending `scala.Phantom`. This object will represent
5955
a universe of phantom types that is completely separated from types in `scala.Any` or other
60-
phantom universes. We can define our phantom universe `MyPhantoms`.
56+
phantom universes.
57+
Let us define our phantom universe `MyPhantoms` for exposition:
6158

6259
```scala
6360
object MyPhantoms extends Phantom
@@ -74,15 +71,15 @@ trait Phantom { // only an `object` can extend this trait
7471

7572
The types in the phantom universe are defined by the top type `Phantom.Any` and bottom type
7673
`Phantom.Nothing`. This means that `MyPhantoms.Any` and `MyPhantoms.Nothing` are the bounds
77-
of the phantom types in `MyPhantoms`, these bounds are `protected` and can not be accessed
74+
of the phantom types in `MyPhantoms`. These bounds are `protected` and can not be accessed
7875
from outside `MyPhantoms` unless an alias is defined for them.
7976

8077
New phantom types can be defined using `type XYZ <: OtherPhantom` (where `>: MyPhantom.Nothing`
81-
will be inferred), this would be the equivalent of `class XYZ extends OtherClass` on a types
82-
only (no runtime definitions). Or aliased with `type MyAny = OtherPhantom`. Whitin `MyPhantoms`
83-
it is possible to refer to `MyPhantoms.Any` and `MyPhantoms.Nothing` with `this.Any` and
84-
`this.Nothing` (or just `Any` and `Nothing` but not recommended). Using this we will define
85-
four the four phantoms: `Inky`, `Blinky`, `Pinky` and `Clyde`.
78+
will be inferred). This would be the equivalent of `class XYZ extends OtherClass` on types
79+
only (no runtime definitions). Additionally, phantom types can be aliased with `type MyAny = OtherPhantom`.
80+
Within `MyPhantoms` it is possible to refer to `MyPhantoms.Any` and `MyPhantoms.Nothing` with `this.Any` and
81+
`this.Nothing` (or just `Any` and `Nothing`, although not recommended).
82+
Using this we will define the following four phantoms: `Inky`, `Blinky`, `Pinky` and `Clyde`.
8683

8784
```scala
8885
object MyPhantoms extends Phantom {
@@ -93,10 +90,10 @@ object MyPhantoms extends Phantom {
9390
}
9491
```
9592

96-
Values of phantom type can be created using the `protected def assume`. This value can be
97-
used as a value of this phantom type as it's type is `this.Nothig` (or `MyPhantoms.Nothing`).
98-
Usually this value will be used to define a `implicit def` that returns the phantom with a more
99-
precise type. In our example we will only create values of type `Pinky` and `Clyde`
93+
Values of phantom types can be created using the `protected def assume`. This value can be
94+
used as the value of a phantom type since its bottom type is `this.Nothing` (or `MyPhantoms.Nothing`).
95+
Usually this value will be used to define an `implicit def` that returns a phantom with a more
96+
precise type. In our example we will only create values of type `Pinky` and `Clyde`:
10097

10198
```scala
10299
object MyPhantoms extends Phantom {
@@ -109,7 +106,7 @@ object MyPhantoms extends Phantom {
109106

110107
### Using the phantoms
111108

112-
From the user of the phantom type there is almost no difference, except for stronger type guarantees.
109+
From the user's perspective, the difference between a type and a phantom type is that the latter is used when we need stronger type guarantees.
113110
We can look at the following simple application:
114111

115112
```scala
@@ -123,23 +120,23 @@ object MyApp {
123120
}
124121
```
125122

126-
Note given the way we defined the phantoms it is impossible to call the `hide` as we did not
127-
expose any value of type `Blinky` to the user. We cannot call `hide(MyPhantoms.assume)` as
128-
`assume` is protected, `hide(null.asInstanceOf[Blinky])` does not compile because it is impossible
129-
to cast to a phantom and `hide(throw new Exception)` (or `hide(???)`) does not compile as `throw` of
130-
type `scala.Nothing` is not in the same type universe as `Blinky`. Good, we caught all possible
131-
mistakes before when compiling the code, no surprises at runtime (hopefully not in production).
123+
Note that given the way we defined the phantoms it is impossible to call the `hide` as we did not
124+
expose any value of type `Blinky` to the user. On the contrary, we cannot call `hide(MyPhantoms.assume)` as
125+
`assume` is protected and `hide(null.asInstanceOf[Blinky])` does not compile because it is impossible
126+
to cast to a phantom. `hide(throw new Exception)` (or `hide(???)`) does not compile as `throw` of
127+
type `scala.Nothing` is not in the same type universe as `Blinky`.
128+
Good, we caught all possible mistakes before when compiling the code, no surprises at runtime (hopefully not in production).
132129

133130

134131
What happens with Phantoms at runtime?
135132
--------------------------------------
136133

137134
Disclaimer: Most of phantom erasure is implemented, but not all of is has been merged in `dotty/master` yet.
138135

139-
As phantom have no effect on the result of a method invocation we just remove them for the call an definition.
140-
The evaluation of the phantom parameter is still be done unless it can be optimized away.
141-
By removing them we also restrict overloading as `def f()` and `def f(x: MyPhantom)` will
142-
have the same signature in the bytecode, just use different names to avoid this.
136+
As phantoms have no effect on the result of a method invocation we just remove them from the actual call.
137+
The evaluation of the phantom parameter is still being done unless it can be optimized away.
138+
By removing them we also restrict overloading.
139+
`def f()` and `def f(x: MyPhantom)` will have the same signature in the bytecode, just use different names to avoid this.
143140

144141
At runtime the `scala.Phantom` trait will not exist.
145142
* The object extending `Phantom` will not extend it anymore
@@ -159,7 +156,7 @@ object MyOtherPhantom extends Phantom {
159156
}
160157
```
161158

162-
will be compiled to
159+
will be compiled to:
163160

164161
```scala
165162
object MyOtherPhantom {

0 commit comments

Comments
 (0)