|
| 1 | +--- |
| 2 | +layout: sip |
| 3 | +discourse: true |
| 4 | +title: SIP-NN - Right-Associative By-Name Operators |
| 5 | +--- |
| 6 | + |
| 7 | +**By: Stefan Zeiger** |
| 8 | + |
| 9 | +## History |
| 10 | + |
| 11 | +| Date | Version | |
| 12 | +|---------------|---------------| |
| 13 | +| Jul 12th 2017 | Initial Draft | |
| 14 | + |
| 15 | +## Motivation |
| 16 | + |
| 17 | +Scala allows the definition of right-associative by-name operators but the desugaring, as |
| 18 | +currently defined, forces the arguments, thus effectively making them by-value. This has |
| 19 | +been recognized as a [bug](https://github.com/scala/bug/issues/1980) since 2009. |
| 20 | + |
| 21 | +## Motivating Examples |
| 22 | + |
| 23 | +Apart from the examples mentioned in and linked to [scala/bug#1980](https://github.com/scala/bug/issues/1980), |
| 24 | +this has recently come up as a [problem for the collections library redesign](https://github.com/scala/collection-strawman/issues/127) |
| 25 | +for Scala 2.13. |
| 26 | + |
| 27 | +Scala 2.12 has a `Stream` type with a lazy tail and a strict head element. Thanks to a clever |
| 28 | +[hack](https://github.com/scala/scala/blob/9ab72a204ff3070ffabc3c06f3d381999da43fcd/src/library/scala/collection/immutable/Stream.scala#L1115-L1133) |
| 29 | +right-associative by-name operators can work well enough for `Stream`: |
| 30 | + |
| 31 | + scala> def f(i: Int) = { println("Generating "+i); i } |
| 32 | + f: (i: Int)Int |
| 33 | + |
| 34 | + scala> f(1) #:: f(2) #:: f(3) #:: Stream.empty |
| 35 | + Generating 1 |
| 36 | + res0: scala.collection.immutable.Stream[Int] = Stream(1, ?) |
| 37 | + |
| 38 | +The `LazyList` type proposed for the new collections library is supposed to be lazy in the head and tail. |
| 39 | +This cannot be supported with the existing hack (which always forces the first element in the chain), so we need |
| 40 | +a proper fix at the language level. |
| 41 | + |
| 42 | +## Design |
| 43 | + |
| 44 | +The desugaring of binary operators is currently defined in the spec as: |
| 45 | + |
| 46 | +> A left-associative binary |
| 47 | +> operation `e1 op e2` is interpreted as `e1.op(e2)`. If `op` is |
| 48 | +> right-associative, the same operation is interpreted as |
| 49 | +> `{ val x=e1; e2.op(x) }`, where `x` is a fresh name. |
| 50 | +
|
| 51 | +It should be changed to: |
| 52 | + |
| 53 | +> A left-associative binary |
| 54 | +> operation `e1 op e2` is interpreted as `e1.op(e2)`. If `op` is |
| 55 | +> right-associative and its parameter is passed by name, the same operation is interpreted as |
| 56 | +> `e2.op(e1)`. If `op` is right-associative and its parameter is passed by value, |
| 57 | +> it is interpreted as `{ val x=e1; e2.op(x) }`, where `x` is a fresh name. |
| 58 | +
|
| 59 | +This means that all by-value parameters are still forced from left to right but by-name |
| 60 | +parameters are not forced anymore. They now behave the same way in operator syntax as they |
| 61 | +would when using standard method call syntax. |
| 62 | + |
| 63 | +## Implementation |
| 64 | + |
| 65 | +A complete implementation for Scala 2.13 is provided in [scala/scala#5969](https://github.com/scala/scala/pull/5969). |
| 66 | + |
| 67 | +## Counter-Examples |
| 68 | + |
| 69 | +No change of type inference semantics is implied by the new desugaring. In particular, all parameters to |
| 70 | +right-associative operators are still type-checked without an expected type in the current implementation. |
| 71 | + |
| 72 | +It may be desirable to use an expected type, like for a method call, but that is orthogonal to this proposal |
| 73 | +and would necessarily apply equally to by-name and by-value parameters. In the case of overloaded |
| 74 | +operators it cannot be determined whether the parameter is by-name or by-value without type-checking the |
| 75 | +argument first. |
| 76 | + |
| 77 | +## Drawbacks |
| 78 | + |
| 79 | +- This constitutes a silent change in semantics for existing code. Since the current semantics are essentially |
| 80 | + broken the likelihood of affecting existing code negatively are low. |
| 81 | + |
| 82 | +- Macros and tooling that works at the Scala AST level may need to be adapted to the new desugaring. This is also |
| 83 | + unlikely because the new desugaring produces currently legal Scala code that could have been manually written in |
| 84 | + the same way. |
| 85 | + |
| 86 | +## Alternatives |
| 87 | + |
| 88 | +As mentioned above, the current `Stream` |
| 89 | +[hack](https://github.com/scala/scala/blob/9ab72a204ff3070ffabc3c06f3d381999da43fcd/src/library/scala/collection/immutable/Stream.scala#L1115-L1133) |
| 90 | +can work around this problem in some cases but not all. |
0 commit comments