|
| 1 | +--- |
| 2 | +layout: sip |
| 3 | +discourse: true |
| 4 | +title: SIP-33 - Priority-based infix type precedence |
| 5 | + |
| 6 | +vote-status: pending |
| 7 | +permalink: /sips/:title.html |
| 8 | +--- |
| 9 | + |
| 10 | +**By: Oron Port** |
| 11 | + |
| 12 | +## History |
| 13 | + |
| 14 | +| Date | Version | |
| 15 | +| ------------- | ---------------------------------------- | |
| 16 | +| Feb 7th 2017 | Initial Draft | |
| 17 | +| Feb 9th 2017 | Updates from feedback | |
| 18 | +| Feb 10th 2017 | Updates from feedback | |
| 19 | +| Aug 8th 2017 | Numbered SIP, improve view, fixed example, and added related issues | |
| 20 | +| Oct 20th 2017 | Added implementation link | |
| 21 | +| Oct 25th 2017 | Moved prefix types to [another SIP](http://docs.scala-lang.org/sips/adding-prefix-types.html), changed title and PR | |
| 22 | +| Nov 29th 2017 | Updated SIP according to feedback in the PR | |
| 23 | + |
| 24 | + |
| 25 | +Your feedback is welcome! If you're interested in discussing this proposal, head over to [this](https://contributors.scala-lang.org/t/sip-nn-make-infix-type-alias-precedence-like-expression-operator-precedence/471) Scala Contributors thread and let me know what you think. |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +## Introduction |
| 30 | +Currently scala allows symbol operators (`-`, `*`, `~~>`, etc.) for both type names and definition names. |
| 31 | +Unfortunately, there is a 'surprise' element since the two differ in behavior. While infix types are 'mostly' left-associative, the expression operation precedence is determined by the operator's first character (e.g., `/` is precedent to `+`). Please see [Infix Types](http://scala-lang.org/files/archive/spec/2.12/03-types.html#infix-types) and [Infix Operations](http://scala-lang.org/files/archive/spec/2.12/06-expressions.html#infix-operations) sections of the Scala specifications for more details. |
| 32 | + |
| 33 | +**Infix expression precedence vs. infix type precedence example**: |
| 34 | + |
| 35 | +```scala |
| 36 | +object InfixExpressionPrecedence { |
| 37 | + case class Nummy(expand : String) { |
| 38 | + def + (that : Nummy) : Nummy = Nummy(s"Plus[$this,$that]") |
| 39 | + def / (that : Nummy) : Nummy = Nummy(s"Div[$this,$that]") |
| 40 | + } |
| 41 | + object N1 extends Nummy("N1") |
| 42 | + object N2 extends Nummy("N2") |
| 43 | + object N3 extends Nummy("N3") |
| 44 | + object N4 extends Nummy("N4") |
| 45 | + //Both expand to Plus[Plus[N1,Div[N2,N3]],N4] |
| 46 | + assert((N1 + N2 / N3 + N4).expand == (N1 + (N2 / N3) + N4).expand) |
| 47 | +} |
| 48 | +object InfixTypePrecedence { |
| 49 | + trait Plus[N1, N2] |
| 50 | + trait Div[N1, N2] |
| 51 | + type +[N1, N2] = Plus[N1, N2] |
| 52 | + type /[N1, N2] = Div[N1, N2] |
| 53 | + trait N1 |
| 54 | + trait N2 |
| 55 | + trait N3 |
| 56 | + trait N4 |
| 57 | + //Error! |
| 58 | + //Left expands to Plus[Div[Plus[N1,N2],N3],N4] (Surprising) |
| 59 | + //Right expands to Plus[Plus[N1,Div[N2,N3]],N4] |
| 60 | + implicitly[(N1 + N2 / N3 + N4) =:= (N1 + (N2 / N3) + N4)] |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## Motivation |
| 67 | +It is easier to reason about the language when mathematical and logical operations for both terms and types are expressed the same. |
| 68 | + |
| 69 | +### Motivating examples |
| 70 | + |
| 71 | +#### Dotty infix type similarity |
| 72 | +Dotty infix type associativity and precedence seem to act the same as expressions. |
| 73 | +No documentation available to prove this, but the infix example above works perfectly in dotty. |
| 74 | + |
| 75 | +Dotty has no prefix types, same as Scalac. |
| 76 | + |
| 77 | +#### Singleton-ops library example |
| 78 | +The [singleton-ops library](https://github.com/fthomas/singleton-ops) with [Typelevel Scala](https://github.com/typelevel/scala) (which implemented [SIP-23](http://docs.scala-lang.org/sips/pending/42.type.html)) enable developers to express literal type operations more intuitively. For example: |
| 79 | + |
| 80 | +```scala |
| 81 | +import singleton.ops._ |
| 82 | + |
| 83 | +val four1 : 4 = implicitly[2 + 2] |
| 84 | +val four2 : 2 + 2 = 4 |
| 85 | +val four3 : 1 + 3 = implicitly[2 + 2] |
| 86 | + |
| 87 | +class MyVec[L] { |
| 88 | + def doubleSize = new MyVec[2 * L] |
| 89 | + def nSize[N] = new MyVec[N * L] |
| 90 | +} |
| 91 | +object MyVec { |
| 92 | + implicit def apply[L](implicit check : Require[L > 0]) : MyVec[L] = new MyVec[L]() |
| 93 | +} |
| 94 | +val myVec : MyVec[10] = MyVec[4 + 1].doubleSize |
| 95 | +val myBadVec = MyVec[-1] //fails compilation, as required |
| 96 | +``` |
| 97 | + |
| 98 | +We currently loose some of the intuitive appeal due to the precedence issue: |
| 99 | + |
| 100 | +```scala |
| 101 | +val works : 1 + (2 * 3) + 4 = 11 |
| 102 | +val fails : 1 + 2 * 3 + 4 = 11 //left associative:(((1+2)*3)+4))) = 13 |
| 103 | +``` |
| 104 | + |
| 105 | +#### Developer issues example |
| 106 | +[This](http://stackoverflow.com/questions/23333882/scala-infix-type-aliasing-for-2-type-parameters) stackoverflow question demonstrate developers are 'surprised' by the difference in infix precedence, expecting infix type precedence to act the same as expression operations. |
| 107 | + |
| 108 | +--- |
| 109 | + |
| 110 | +## Proposal |
| 111 | + |
| 112 | +Make infix types conform to the same precedence and associativity traits as term operations. |
| 113 | + |
| 114 | +------ |
| 115 | + |
| 116 | +## Implementation |
| 117 | + |
| 118 | +A PR for this SIP is available at: [https://github.com/scala/scala/pull/6147](https://github.com/scala/scala/pull/6147) |
| 119 | + |
| 120 | +------ |
| 121 | + |
| 122 | +### Interactions with other language features |
| 123 | + |
| 124 | +#### Star `*` infix type interaction with repeated parameters |
| 125 | +The [repeated argument symbol `*`](https://www.scala-lang.org/files/archive/spec/2.12/04-basic-declarations-and-definitions.html#repeated-parameters) may create confusion with the infix type `*`. |
| 126 | +Please note that this feature interaction already exists within the current specification. |
| 127 | + |
| 128 | +```scala |
| 129 | +trait +[N1, N2] |
| 130 | +trait *[N1, N2] |
| 131 | +trait N1 |
| 132 | +trait N2 |
| 133 | +def foo(a : N1*N1+N2*) : Unit = {} //repeated parameter of type +[*[N1, N1], N2] |
| 134 | +``` |
| 135 | + |
| 136 | +However, it is very unlikely that such interaction would occur. |
| 137 | + |
| 138 | +**Related Issues** |
| 139 | + |
| 140 | +* [Dotty Issue #1961](https://github.com/lampepfl/dotty/issues/1961) |
| 141 | + |
| 142 | + |
| 143 | +## Backward Compatibility |
| 144 | +Changing infix type associativity and precedence affects code that uses type operations and conforms to the current specification. |
| 145 | + |
| 146 | +Note: changing the infix precedence didn't fail any scalac test. |
| 147 | + |
| 148 | +--- |
| 149 | + |
| 150 | +### Bibliography |
| 151 | +[Scala Contributors](https://contributors.scala-lang.org/t/sip-nn-make-infix-type-alias-precedence-like-expression-operator-precedence/471) |
| 152 | + |
| 153 | +[scala-sips](https://groups.google.com/forum/#!topic/scala-sips/ARVf1RLDw9U) |
0 commit comments