|
| 1 | +--- |
| 2 | +layout: sip |
| 3 | +disqus: true |
| 4 | +title: SIP-21 - Spores |
| 5 | +--- |
| 6 | + |
| 7 | +**By: Heather Miller, Martin Odersky, and Philipp Haller** |
| 8 | + |
| 9 | +Functional programming languages are regularly touted as an enabling force, as an increasing number of applications become concurrent and distributed. However, managing closures in a concurrent or distributed environment, or writing APIs to be used by clients in such an environment, remains considerably precarious-- complicated environments can be captured by these closures, which regularly leads to a whole host of potential hazards across libraries/frameworks in Scala's standard library and its ecosystem. |
| 10 | + |
| 11 | +Potential hazards when using closures incorrectly: |
| 12 | + |
| 13 | +- Memory leaks |
| 14 | +- Race conditions, due to capturing mutable references |
| 15 | +- Runtime serialization errors, due to unintended capture of references |
| 16 | + |
| 17 | +This SIP outlines an abstraction, called _spores_, which enables safer use of closures in concurrent and distributed environments. This is achieved by controlling the environment which a spore can capture. Using an _assignment-on-capture_ semantics, certain concurrency bugs due to capturing mutable references can be avoided. |
| 18 | + |
| 19 | +## Motivating Examples |
| 20 | + |
| 21 | +### Futures and Akka Actors |
| 22 | + |
| 23 | +In the following example, an Akka actor spawns a future to concurrently process incoming requests. |
| 24 | + |
| 25 | +**Example 1:** |
| 26 | + |
| 27 | + def receive = { |
| 28 | + case Request(data) => |
| 29 | + future { |
| 30 | + val result = transform(data) |
| 31 | + sender ! Response(result) |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | +Capturing `sender` in the above example is problematic, since it does not return a stable value. It is possible that the future's body is executed at a time when the actor has started processing the next `Request` message which could be originating from a different actor. As a result, the `Response` message of the future might be sent to the wrong receiver. |
| 36 | + |
| 37 | + |
| 38 | +### Serialization |
| 39 | + |
| 40 | +The following example uses Java Serialization to serialize a closure. However, serialization fails with a `NotSerializableException` due to the unintended capture of a reference to an enclosing object. |
| 41 | + |
| 42 | +**Example 2:** |
| 43 | + |
| 44 | + case class Helper(name: String) |
| 45 | + |
| 46 | + class Main { |
| 47 | + val helper = Helper("the helper") |
| 48 | + |
| 49 | + val fun: Int => Unit = (x: Int) => { |
| 50 | + val result = x + " " + helper.toString |
| 51 | + println("The result is: " + result) |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | +Given the above class definitions, serializing the `fun` member of an instance of `Main` throws a `NotSerializableException`. This is unexpected, since `fun` refers only to serializable objects: `x` (an `Int`) and `helper` (an instance of a case class). |
| 56 | + |
| 57 | +Here is an explanation of why the serialization of `fun` fails: since `helper` is a field, it is not actually copied when it is captured by the closure. Instead, when accessing helper its getter is invoked. This can be made explicit by replacing `helper.toString` by the invocation of its getter, `this.helper.toString`. Consequently, the `fun` closure captures `this`, not just a copy of `helper`. However, `this` is a reference to class `Main` which is not serializable. |
| 58 | + |
| 59 | +The above example is not the only possible situation in which a closure can capture a reference to `this` or to an enclosing object in an unintended way. Thus, runtime errors when serializing closures are common. |
| 60 | + |
| 61 | +## Design |
| 62 | + |
| 63 | +The main idea behind spores is to provide an alternative way to create closure-like objects, in a way where the environment is controlled. |
| 64 | + |
| 65 | +A spore is created as follows. |
| 66 | + |
| 67 | +**Example 3:** |
| 68 | + |
| 69 | + val s = spore { |
| 70 | + val h = helper |
| 71 | + (x: Int) => { |
| 72 | + val result = x + " " + h.toString |
| 73 | + println("The result is: " + result) |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | +The body of a spore consists of two parts: |
| 78 | + |
| 79 | +1. a sequence of local value (val) declarations only, and |
| 80 | +2. a closure. |
| 81 | + |
| 82 | +In general, a `spore { ... }` expression has the following shape. |
| 83 | + |
| 84 | +Note that the value declarations described in point 1 above can be `implicit` but not `lazy`. |
| 85 | + |
| 86 | +**Figure 1:** |
| 87 | + |
| 88 | + spore { |
| 89 | + val x_1: T_1 = init_1 |
| 90 | + ... |
| 91 | + val x_n: T_n = init_n |
| 92 | + (p_1: S_1, ..., p_m: S_m) => { |
| 93 | + <body> |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | +The types `T_1, ..., T_n` can also be inferred. |
| 98 | + |
| 99 | +The closure of a spore has to satisfy the following rule. All free variables of the closure body have to be either |
| 100 | + |
| 101 | +1. parameters of the closure, or |
| 102 | +2. declared in the preceding sequence of local value declarations. |
| 103 | + |
| 104 | +**Example 4:** |
| 105 | + |
| 106 | + case class Person(name: String, age: Int) |
| 107 | + val outer1 = 0 |
| 108 | + val outer2 = Person("Jim", 35) |
| 109 | + val s = spore { |
| 110 | + val inner = outer2 |
| 111 | + (x: Int) => { |
| 112 | + s"The result is: ${x + inner.age + outer1}" |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | +In the above example, the spore's closure is invalid, and would be rejected during compilation. The reason is that the variable `outer1` is neither a parameter of the closure nor one of the spore's value declarations (the only value declaration is: `val inner = outer2`). |
| 117 | + |
| 118 | +### Spore Type |
| 119 | + |
| 120 | +The type of the spore is determined by the type and arity of the closure. If the closure has type |
| 121 | +`A => B`, then the spore has type `Spore[A, B]`. For convenience we also define spore types for two or more parameters. |
| 122 | + |
| 123 | +In example 3, the type of s is `Spore[Int, Unit]`. |
| 124 | +Implementation |
| 125 | +The spore construct is a macro which |
| 126 | + |
| 127 | +- performs the checking described above, and which |
| 128 | +- replaces the spore body so that it creates an instance of one of the Spore traits, according to the arity of the closure of the spore. |
| 129 | + |
| 130 | +The `Spore` trait for spores of arity 1 is declared as follows: |
| 131 | + |
| 132 | + trait Spore[-T, +R] extends Function1[T, R] |
| 133 | + |
| 134 | +For each function arity there exists a corresponding `Spore` trait of the same arity (called `Spore2`, `Spore3`, etc.) |
| 135 | + |
| 136 | +### Macro Expansion |
| 137 | + |
| 138 | +An invocation of the spore macro expands the spore's body as follows. Given the general shape of a spore as shown above, the spore macro produces the following code: |
| 139 | + |
| 140 | + new <spore implementation class>[S_1, ..., S_m, R]({ |
| 141 | + val x_1: T_1 = init_1 |
| 142 | + ... |
| 143 | + val x_n: T_n = init_n |
| 144 | + (p_1: S_1, ..., p_m: S_m) => { |
| 145 | + <body> |
| 146 | + } |
| 147 | + }) |
| 148 | + |
| 149 | +Note that, after checking, the spore macro need not do any further transformation, since implementation details such as unneeded remaining outer references are removed by the new backend intended for inclusion in Scala 2.11. It's also useful to note that in some cases these unwanted outer references are already removed by the existing backend. |
| 150 | + |
| 151 | +The spore implementation classes follow a simple pattern. For example, for arity 1, the implementation class is declared as follows: |
| 152 | + |
| 153 | + class SporeImpl[-T, +R](f: T => R) extends Spore[T, R] { |
| 154 | + def apply(x: T): R = f(x) |
| 155 | + } |
| 156 | + |
| 157 | +### Type Inference |
| 158 | + |
| 159 | +Similar to regular functions and closures, the type of a spore should be inferred. Inferring the type of a spore amounts to inferring the type arguments when instantiating a spore implementation class: |
| 160 | + |
| 161 | + new <spore implementation class>[S_1, ..., S_m, R]({ |
| 162 | + // ... |
| 163 | + }) |
| 164 | + |
| 165 | +In the above expression, the type arguments `S_1, ..., S_m`, and `R` should be inferred from the expected type. |
| 166 | + |
| 167 | +Our current proposal is to solve this type inference problem in the context of the integration of Java SAM closures into Scala. Given that it is planned to eventually support such closures, and to support type inference for these closures as well, we plan to piggyback on the work done on type inference for SAMs in general to achieve type inference for spores. |
| 168 | + |
| 169 | +## Motivating Examples, Revisited |
| 170 | + |
| 171 | +We now revisit the motivating examples we described in the above section, this time in the context of spores. |
| 172 | + |
| 173 | +### Futures and Akka actors |
| 174 | + |
| 175 | +Using spores, example 1 can be re-written as follows: |
| 176 | + |
| 177 | + def receive = { |
| 178 | + case Request(data) => |
| 179 | + val s = spore { |
| 180 | + val from = sender |
| 181 | + val d = data |
| 182 | + () => { |
| 183 | + val result = transform(d) |
| 184 | + from ! Response(result) |
| 185 | + } |
| 186 | + } |
| 187 | + future { s() } |
| 188 | + } |
| 189 | + |
| 190 | +In this case, the problematic capturing of `this` is avoided, since the result of `this.sender` is assigned to the spore's local value `from` when the spore is created. The spore conformity checking ensures that within the spore's closure, only `from` and `d` are used. |
| 191 | + |
| 192 | +### Serialization |
| 193 | + |
| 194 | +Using spores, example 2 can be re-written as follows: |
| 195 | + |
| 196 | + case class Helper(name: String) |
| 197 | + |
| 198 | + class Main { |
| 199 | + val helper = Helper("the helper") |
| 200 | + |
| 201 | + val fun: Spore[Int, Unit] = spore { |
| 202 | + val h = helper |
| 203 | + (x: Int) => { |
| 204 | + val result = x + " " + h.toString |
| 205 | + println("The result is: " + result) |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | +Similar to example 1, the problematic capturing of `this` is avoided, since `helper` has to be assigned to a local value (here, `h`) so that it can be used inside the spore's closure. As a result, `fun` can now be serialized without runtime errors, since `h` refers to a serializable object (a case class instance). |
| 211 | + |
0 commit comments