|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: "Erased Terms" |
| 4 | +--- |
| 5 | + |
| 6 | +# Why erased terms? |
| 7 | + |
| 8 | +Let's describe the motivation behind erased terms with an example. In the |
| 9 | +following we show a simple state machine which can be in a state `On` or `Off`. |
| 10 | +The machine can change state from `Off` to `On` with `turnedOn` only if it is |
| 11 | +currently `Off`. This last constraint is captured with the `IsOff[S]` implicit |
| 12 | +evidence which only exists for `IsOff[Off]`. For example, not allowing calling |
| 13 | +`turnedOn` on in an `On` state as we would require an evidence of type |
| 14 | +`IsOff[On]` that will not be found. |
| 15 | + |
| 16 | +```scala |
| 17 | +sealed trait State |
| 18 | +final class On extends State |
| 19 | +final class Off extends State |
| 20 | + |
| 21 | +@implicitNotFound("State is must be Off") |
| 22 | +class IsOff[S <: State] |
| 23 | +object IsOff { |
| 24 | + implicit def isOff: IsOff[Off] = new IsOff[Off] |
| 25 | +} |
| 26 | + |
| 27 | +class Machine[S <: State] { |
| 28 | + def turnedOn(implicit ev: IsOff[S]): Machine[On] = new Machine[On] |
| 29 | +} |
| 30 | + |
| 31 | +val m = new Machine[Off] |
| 32 | +m.turnedOn |
| 33 | +m.turnedOn.turnedOn // ERROR |
| 34 | +// ^ |
| 35 | +// State is must be Off |
| 36 | +``` |
| 37 | + |
| 38 | +Note that in the code above the actual implicit arguments for `IsOff` are never |
| 39 | +used at runtime; they serve only to establish the right constraints at compile |
| 40 | +time. As these terms are never used at runtime there is not real need to have |
| 41 | +them around, but they still need to be present in some form in the generated |
| 42 | +code to be able to do separate compilation and retain binary compatibility. We |
| 43 | +introduce _erased terms_ to overcome this limitation: we are able to enforce the |
| 44 | +right constrains on terms at compile time. These terms have no run time |
| 45 | +semantics and they are completely erased. |
| 46 | + |
| 47 | +# How to define erased terms? |
| 48 | + |
| 49 | +Parameters of methods and functions can be declared as erased, placing `erased` |
| 50 | +in front of a parameter list (like `given`). |
| 51 | + |
| 52 | +```scala |
| 53 | +def methodWithErasedEv erased (ev: Ev): Int = 42 |
| 54 | + |
| 55 | +val lambdaWithErasedEv: erased Ev => Int = |
| 56 | + erased (ev: Ev) => 42 |
| 57 | +``` |
| 58 | + |
| 59 | +`erased` parameters will not be usable for computations, though they can be used |
| 60 | +as arguments to other `erased` parameters. |
| 61 | + |
| 62 | +```scala |
| 63 | +def methodWithErasedInt1 erased (i: Int): Int = |
| 64 | + i + 42 // ERROR: can not use i |
| 65 | + |
| 66 | +def methodWithErasedInt2 erased (i: Int): Int = |
| 67 | + methodWithErasedInt1(i) // OK |
| 68 | +``` |
| 69 | + |
| 70 | +Not only parameters can be marked as erased, `val` and `def` can also be marked |
| 71 | +with `erased`. These will also only be usable as arguments to `erased` |
| 72 | +parameters. |
| 73 | + |
| 74 | +```scala |
| 75 | +erased val erasedEvidence: Ev = ... |
| 76 | +methodWithErasedEv(erasedEvidence) |
| 77 | +``` |
| 78 | + |
| 79 | +# What happens with erased values at runtime? |
| 80 | + |
| 81 | +As `erased` are guaranteed not to be used in computations, they can and will be |
| 82 | +erased. |
| 83 | + |
| 84 | +```scala |
| 85 | +// becomes def methodWithErasedEv(): Int at runtime |
| 86 | +def methodWithErasedEv erased (ev: Ev): Int = ... |
| 87 | + |
| 88 | +def evidence1: Ev = ... |
| 89 | +erased def erasedEvidence2: Ev = ... // does not exist at runtime |
| 90 | +erased val erasedEvidence3: Ev = ... // does not exist at runtime |
| 91 | + |
| 92 | +// evidence1 is not evaluated and no value is passed to methodWithErasedEv |
| 93 | +methodWithErasedEv(evidence1) |
| 94 | +``` |
| 95 | + |
| 96 | +# State machine with erased evidence example |
| 97 | + |
| 98 | +The following example is an extended implementation of a simple state machine |
| 99 | +which can be in a state `On` or `Off`. The machine can change state from `Off` |
| 100 | +to `On` with `turnedOn` only if it is currently `Off`, conversely from `On` to |
| 101 | +`Off` with `turnedOff` only if it is currently `On`. These last constraint are |
| 102 | +captured with the `IsOff[S]` and `IsOn[S]` implicit evidence only exist for |
| 103 | +`IsOff[Off]` and `IsOn[On]`. For example, not allowing calling `turnedOff` on in |
| 104 | +an `Off` state as we would require an evidence `IsOn[Off]` that will not be |
| 105 | +found. |
| 106 | + |
| 107 | +As the implicit evidences of `turnedOn` and `turnedOff` are not used in the |
| 108 | +bodies of those functions we can mark them as `erased`. This will remove the |
| 109 | +evidence parameters at runtime, but we would still evaluate the `isOn` and |
| 110 | +`isOff` implicits that were found as arguments. As `isOn` and `isOff` are not |
| 111 | +used except as `erased` arguments, we can mark them as `erased`, hence removing |
| 112 | +the evaluation of the `isOn` and `isOff` evidences. |
| 113 | + |
| 114 | +```scala |
| 115 | +import scala.annotation.implicitNotFound |
| 116 | + |
| 117 | +sealed trait State |
| 118 | +final class On extends State |
| 119 | +final class Off extends State |
| 120 | + |
| 121 | +@implicitNotFound("State is must be Off") |
| 122 | +class IsOff[S <: State] |
| 123 | +object IsOff { |
| 124 | + // def isOff will not be called at runtime for turnedOn, the compiler will only require that this evidence exists |
| 125 | + implicit def isOff: IsOff[Off] = new IsOff[Off] |
| 126 | +} |
| 127 | + |
| 128 | +@implicitNotFound("State is must be On") |
| 129 | +class IsOn[S <: State] |
| 130 | +object IsOn { |
| 131 | + // erased val isOn will not exist at runtime, the compiler will only require that this evidence exists at compile time |
| 132 | + erased implicit val isOn: IsOn[On] = new IsOn[On] |
| 133 | +} |
| 134 | + |
| 135 | +class Machine[S <: State] private { |
| 136 | + // ev will disappear from both functions |
| 137 | + def turnedOn given erased (ev: IsOff[S]): Machine[On] = new Machine[On] |
| 138 | + def turnedOff given erased (ev: IsOn[S]): Machine[Off] = new Machine[Off] |
| 139 | +} |
| 140 | + |
| 141 | +object Machine { |
| 142 | + def newMachine(): Machine[Off] = new Machine[Off] |
| 143 | +} |
| 144 | + |
| 145 | +object Test { |
| 146 | + def main(args: Array[String]): Unit = { |
| 147 | + val m = Machine.newMachine() |
| 148 | + m.turnedOn |
| 149 | + m.turnedOn.turnedOff |
| 150 | + |
| 151 | + // m.turnedOff |
| 152 | + // ^ |
| 153 | + // State is must be On |
| 154 | + |
| 155 | + // m.turnedOn.turnedOn |
| 156 | + // ^ |
| 157 | + // State is must be Off |
| 158 | + } |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +Note that in [Inline](./inline.html) we discussed `erasedValue` and inline |
| 163 | +matches. `erasedValue` is implemented with `erased`, so the state machine above |
| 164 | +can be encoded as follows: |
| 165 | + |
| 166 | +```scala |
| 167 | +import scala.compiletime._ |
| 168 | + |
| 169 | +sealed trait State |
| 170 | +final class On extends State |
| 171 | +final class Off extends State |
| 172 | + |
| 173 | +class Machine[S <: State] { |
| 174 | + inline def turnOn() <: Machine[On] = inline erasedValue[S] match { |
| 175 | + case _: Off => new Machine[On] |
| 176 | + case _: On => error("Turning on an already turned on machine") |
| 177 | + } |
| 178 | + inline def turnOff() <: Machine[Off] = inline erasedValue[S] match { |
| 179 | + case _: On => new Machine[Off] |
| 180 | + case _: Off => error("Turning off an already turned off machine") |
| 181 | + } |
| 182 | +} |
| 183 | + |
| 184 | +object Machine { |
| 185 | + def newMachine(): Machine[Off] = { |
| 186 | + println("newMachine") |
| 187 | + new Machine[Off] |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +object Test { |
| 192 | + val m = Machine.newMachine() |
| 193 | + m.turnOn() |
| 194 | + m.turnOn().turnOff() |
| 195 | + m.turnOn().turnOn() // error: Turning on an already turned on machine |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +[More Details](./erased-terms-spec.html) |
0 commit comments