Skip to content

Commit 41085c8

Browse files
authored
Merge pull request #2519 from dotty-staging/phantom-doc-1
Add Phantom type docs
2 parents 92b2561 + a5eacd0 commit 41085c8

File tree

6 files changed

+259
-6
lines changed

6 files changed

+259
-6
lines changed

docs/_includes/features.html

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,12 @@ <h1 id="so-features">So, features?</h1>
3737
<td>Implemented</td>
3838
</tr>
3939
<tr>
40-
<td>
41-
<!--<a href="http://dotty.epfl.ch/docs/reference/phantom-types.html">Phantom types</a>-->
42-
Phantom types
43-
</td>
40+
<td><a href="http://dotty.epfl.ch/docs/reference/implicit-function-types.html">Implicit function types</a></td>
4441
<td>Implemented</td>
4542
</tr>
4643
<tr>
47-
<td><a href="http://dotty.epfl.ch/docs/reference/implicit-function-types.html">Implicit function types</a></td>
48-
<td>Implemented</td>
44+
<td><a href="http://dotty.epfl.ch/docs/reference/phantom-types.html">Phantom types</a></td>
45+
<td>In progress</td>
4946
</tr>
5047
<tr>
5148
<td><a href="https://github.com/dotty-linker/dotty">Auto-Specialization</a></td>

docs/docs/reference/phantom-types.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
---
2+
layout: doc-page
3+
title: "Phantom Types"
4+
---
5+
6+
7+
What is a phantom type?
8+
-----------------------
9+
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`.
28+
In fact we allow multiple phantom universes to exist.
29+
30+
```none
31+
+-----+ +---------------+ +--------------------+
32+
| Any | | MyPhantom.Any | | MyOtherPhantom.Any |
33+
+-----+ +---------------+ +--------------------+
34+
| | |
35+
+----+------+ +-----+------+ ...
36+
| | | |
37+
+--------+ +--------+ +------+ +--------+
38+
| AnyRef | | AnyVal | | Inky | | Blinky |
39+
+--------+ +--------+ +------+ +--------+
40+
... ... | |
41+
+------+ | +-------+ |
42+
| Null | | | Pinky | |
43+
+------+ | +-------+ |
44+
| | | |
45+
+------+-----+ +----+------+ ...
46+
| | |
47+
+---------+ +-------------------+ +------------------------+
48+
| Nothing | | MyPhantom.Nothing | | MyOtherPhantom.Nothing |
49+
+---------+ +-------------------+ +------------------------+
50+
```
51+
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+
55+
56+
Implement your own phantom type
57+
-------------------------------
58+
Phantom types are definded by an `object` extending `scala.Phantom`. This object will represent
59+
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`.
61+
62+
```scala
63+
object MyPhantoms extends Phantom
64+
```
65+
66+
```scala
67+
package scala
68+
trait Phantom { // only an `object` can extend this trait
69+
protected final type Any // not a subtype of scala.Any
70+
protected final type Nothing // subtype of every subtype of this.Any
71+
protected final def assume: this.Nothig
72+
}
73+
```
74+
75+
The types in the phantom universe are defined by the top type `Phantom.Any` and bottom type
76+
`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
78+
from outside `MyPhantoms` unless an alias is defined for them.
79+
80+
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`.
86+
87+
```scala
88+
object MyPhantoms extends Phantom {
89+
type Inky <: this.Any
90+
type Blinky <: this.Any
91+
type Pinky <: Inky
92+
type Clyde <: Pinky
93+
}
94+
```
95+
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`
100+
101+
```scala
102+
object MyPhantoms extends Phantom {
103+
... // Type definition
104+
105+
def pinky: Pinky = assume
106+
def clyde: Clyde = assume
107+
}
108+
```
109+
110+
### Using the phantoms
111+
112+
From the user of the phantom type there is almost no difference, except for stronger type guarantees.
113+
We can look at the following simple application:
114+
115+
```scala
116+
import MyPhantoms._
117+
object MyApp {
118+
def run(phantom: Inky) = println("run")
119+
def hide(phantom: Blinky) = println("run")
120+
121+
run(pinky)
122+
run(clyde)
123+
}
124+
```
125+
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).
132+
133+
134+
What happens with Phantoms at runtime?
135+
--------------------------------------
136+
137+
Disclaimer: Most of phantom erasure is implemented, but not all of is has been merged in `dotty/master` yet.
138+
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.
143+
144+
At runtime the `scala.Phantom` trait will not exist.
145+
* The object extending `Phantom` will not extend it anymore
146+
* All phantom types will be erased on a single erased type (important in overloading for methods returning a phantom)
147+
* Calls to `Phantom.assume` will become a reference to a singleton of the erased phantom type and will be removed wherever possible
148+
149+
```scala
150+
object MyOtherPhantom extends Phantom {
151+
type MyPhantom <: this.Any
152+
def myPhantom: MyPhantom = assume
153+
154+
def f1(a: Int, b: MyPhantom, c: Int): Int = a + c
155+
156+
def f2 = {
157+
f1(3, myPhantom, 2)
158+
}
159+
}
160+
```
161+
162+
will be compiled to
163+
164+
```scala
165+
object MyOtherPhantom {
166+
def myPhantom(): <ErasedPhantom> = <ErasedPhantom.UNIT>
167+
168+
def f1(a: Int, c: Int): Int = a + c
169+
170+
def f2 = {
171+
val a$ = 3
172+
myPhantom()
173+
val b$ = 3
174+
f1(a$, b$)
175+
}
176+
}
177+
```
178+
179+
Note that `myPhantom` is not removed as it could have some side effect before returning the phantom.
180+
To remove it just use `inline def myPhantom` instead this will remove the call and allow the
181+
`<ErasedPhantom.UNIT>` to be optimized away.

docs/sidebar.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ sidebar:
2525
url: docs/reference/type-lambdas.html
2626
- title: Implicit Function Types
2727
url: docs/reference/implicit-function-types.html
28+
- title: Phantom Types
29+
url: docs/reference/phantom-types.html
2830
- title: Enums
2931
subsection:
3032
- title: Enumerations
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object MyPhantoms extends Phantom {
2+
type Inky <: this.Any
3+
type Blinky <: this.Any
4+
type Pinky <: Inky
5+
type Clyde <: Pinky
6+
7+
def pinky: Pinky = assume
8+
def clyde: Clyde = assume
9+
}
10+
11+
import MyPhantoms._
12+
object MyApp {
13+
def run(phantom: Inky) = println("run")
14+
def hide(phantom: Blinky) = println("run")
15+
16+
run(pinky)
17+
run(clyde)
18+
19+
hide(pinky) // error
20+
hide(clyde) // error
21+
hide(MyPhantoms.assume) // error
22+
hide(throw new Exception) // error
23+
hide(???) // error
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object MyPhantoms extends Phantom {
2+
type Inky <: this.Any
3+
type Blinky <: this.Any
4+
type Pinky <: Inky
5+
type Clyde <: Pinky
6+
7+
def pinky: Pinky = assume
8+
def clyde: Clyde = assume
9+
}
10+
11+
import MyPhantoms._
12+
object MyApp {
13+
def run(phantom: Inky) = println("run")
14+
def hide(phantom: Blinky) = println("run")
15+
16+
run(pinky)
17+
run(clyde)
18+
19+
hide(null.asInstanceOf[Blinky]) // error
20+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
object MyPhantoms extends Phantom {
2+
type Inky <: this.Any
3+
type Blinky <: this.Any
4+
type Pinky <: Inky
5+
type Clyde <: Pinky
6+
7+
def pinky: Pinky = assume
8+
def clyde: Clyde = assume
9+
}
10+
11+
import MyPhantoms._
12+
object MyApp {
13+
def run(phantom: Inky) = println("run")
14+
def hide(phantom: Blinky) = println("run")
15+
16+
run(pinky)
17+
run(clyde)
18+
}
19+
20+
object MyOtherPhantom extends Phantom {
21+
type MyPhantom <: this.Any
22+
def myPhantom: MyPhantom = assume
23+
24+
def f1(a: Int, b: MyPhantom, c: Int): Int = a + c
25+
26+
def f2 = {
27+
f1(3, myPhantom, 2)
28+
}
29+
}

0 commit comments

Comments
 (0)