8000 Merge pull request #855 from soronpo/master · scala/docs.scala-lang@e8ee124 · GitHub
[go: up one dir, main page]

Skip to content

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e8ee124

Browse files
Merge pull request #855 from soronpo/master
Changes to SIP33
2 parents 2af0bb4 + 614c1f5 commit e8ee124

File tree

1 file changed

+133
-104
lines changed

1 file changed

+133
-104
lines changed

_sips/sips/2017-02-07-make-types-behave-like-expressions.md

Lines changed: 133 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: sip
33
discourse: true
4-
title: SIP-NN - Match infix & prefix types to meet expression rules
4+
title: SIP-33 - Match infix & prefix types to meet expression rules
55

66
vote-status: pending
77
permalink: /sips/:title.html
@@ -11,11 +11,13 @@ permalink: /sips/:title.html
1111

1212
## History
1313

14-
| Date | Version |
15-
|---------------|--------------------------|
16-
| Feb 7th 2017 | Initial Draft |
17-
| Feb 9th 2017 | Updates from feedback |
18-
| Feb 10th 2017 | Updates from feedback |
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+
1921

2022
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.
2123

@@ -30,70 +32,70 @@ Infix types are 'mostly' left-associative,
3032
while the expression operation precedence is determined by the operator's first character (e.g., `/` is precedent to `+`).
3133
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.
3234

33-
**Example**:
34-
35-
{% highlight 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[Plus[N1,Div[N2,N3]],N4] (Surprising)
59-
//Right expands to Plus[Div[Plus[N1,N2],N3],N4]
60-
implicitly[(N1 + N2 / N3 + N4) =:= (N1 + (N2 / N3) + N4)]
61-
}
62-
{% endhighlight %}
35+
**Infix expression precedence vs. infix type precedence example**:
36+
37+
```scala
38+
object InfixExpressionPrecedence {
39+
case class Nummy(expand : String) {
40+
def + (that : Nummy) : Nummy = Nummy(s"Plus[$this,$that]")
41+
def / (that : Nummy) : Nummy = Nummy(s"Div[$this,$that]")
42+
}
43+
object N1 extends Nummy("N1")
44+
object N2 extends Nummy("N2")
45+
object N3 extends Nummy("N3")
46+
object N4 extends Nummy("N4")
47+
//Both expand to Plus[Plus[N1,Div[N2,N3]],N4]
48+
assert((N1 + N2 / N3 + N4).expand == (N1 + (N2 / N3) + N4).expand)
49+
}
50+
object InfixTypePrecedence {
51+
trait Plus[N1, N2]
52+
trait Div[N1, N2]
53+
type +[N1, N2] = Plus[N1, N2]
54+
type /[N1, N2] = Div[N1, N2]
55+
trait N1
56+
trait N2
57+
trait N3
58+
trait N4
59+
//Error!
60+
//Left expands to Plus[Div[Plus[N1,N2],N3],N4] (Surprising)
61+
//Right expands to Plus[Plus[N1,Div[N2,N3]],N4]
62+
implicitly[(N1 + N2 / N3 + N4) =:= (N1 + (N2 / N3) + N4)]
63+
}
64+
```
6365

6466
### Prefix operators bracketless unary use
6567
While expressions have prefix unary operators, there are none for types. See the [Prefix Operations](http://scala-lang.org/files/archive/spec/2.12/06-expressions.html#prefix-operations) section of the Scala specification.
6668
This is a lacking feature of the type language Scala offers. See also interactions of this feature with other Scala features, further down this text.
6769

6870

69-
**Example**:
70-
71-
{% highlight scala %}
72-
object PrefixExpression {
73-
case class Nummy(expand : String) {
74-
def unary_- : Nummy = Nummy(s"-$this")
75-
def unary_~ : Nummy = Nummy(s"~$this")
76-
def unary_! : Nummy = Nummy(s"!$this")
77-
def unary_+ : Nummy = Nummy(s"+$this")
78-
}
79-
object N extends Nummy("N")
80-
val n1 = -N
81-
val n2 = ~N
82-
val n3 = !N
83-
val n4 = +N
84-
}
85-
object NonExistingPrefixTypes {
86-
trait unary_-[A]
87-
trait unary_~[A]
88-
trait unary_![A]
89-
trait unary_+[A]
90-
trait N
91-
type N1 = -N //Not working
92-
type N2 = ~N //Not working
93-
type N3 = !N //Not working
94-
type N4 = +N //Not working
95-
}
96-
{% endhighlight %}
71+
**Prefix expression vs. prefix type example**:
72+
73+
```scala
74+
object PrefixExpression {
75+
case class Nummy(expand : String) {
76+
def unary_- : Nummy = Nummy(s"-$this")
77+
def unary_~ : Nummy = Nummy(s"~$this")
78+
def unary_! : Nummy = Nummy(s"!$this")
79+
def unary_+ : Nummy = Nummy(s"+$this")
80+
}
81+
object N extends Nummy("N")
82+
val n1 = -N
83+
val n2 = ~N
84+
val n3 = !N
85+
val n4 = +N
86+
}
87+
object NonExistingPrefixTypes {
88+
trait unary_-[A]
89+
trait unary_~[A]
90+
trait unary_![A]
91+
trait unary_+[A]
92+
trait N
93+
type N1 = -N //Not working
94+
type N2 = ~N //Not working
95+
type N3 = !N //Not working
96+
type N4 = +N //Not working
97+
}
98+
```
9799

98100
---
99101

@@ -124,30 +126,30 @@ Dotty has no prefix types, same as Scalac.
124126
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)) enables developers to express literal type operations more intuitively.
125127
For example:
126128

127-
{% highlight scala %}
128-
import singleton.ops._
129+
```scala
130+
import singleton.ops._
129131

130-
val four1 : 4 = implicitly[2 + 2]
131-
val four2 : 2 + 2 = 4
132-
val four3 : 1 + 3 = implicitly[2 + 2]
132+
val four1 : 4 = implicitly[2 + 2]
133+
val four2 : 2 + 2 = 4
134+
val four3 : 1 + 3 = implicitly[2 + 2]
133135

134-
class MyVec[L] {
135-
def doubleSize = new MyVec[2 * L]
136-
def nSize[N] = new MyVec[N * L]
137-
}
138-
object MyVec {
139-
implicit def apply[L](implicit check : Require[L > 0]) : MyVec[L] = new MyVec[L]()
140-
}
141-
val myVec : MyVec[10] = MyVec[4 + 1].doubleSize
142-
val myBadVec = MyVec[-1] //fails compilation, as required
143-
{% endhighlight %}
136+
class MyVec[L] {
137+
def doubleSize = new MyVec[2 * L]
138+
def nSize[N] = new MyVec[N * L]
139+
}
140+
object MyVec {
141+
implicit def apply[L](implicit check : Require[L > 0]) : MyVec[L] = new MyVec[L]()
142+
}
143+
val myVec : MyVec[10] = MyVec[4 + 1].doubleSize
144+
val myBadVec = MyVec[-1] //fails compilation, as required
145+
```
144146

145147
We currently loose some of the intuitive appeal due to the precedence issue:
146148

147-
{% highlight scala %}
148-
val works : 1 + (2 * 3) + 4 = 11
149-
val fails : 1 + 2 * 3 + 4 = 11 //left associative:(((1+2)*3)+4))) = 13
150-
{% endhighlight %}
149+
```scala
150+
val works : 1 + (2 * 3) + 4 = 11
151+
val fails : 1 + 2 * 3 + 4 = 11 //left associative:(((1+2)*3)+4))) = 13
152+
```
151153

152154
#### Developer issues example
153155
[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.
@@ -160,40 +162,67 @@ We currently loose some of the intuitive appeal due to the precedence issue:
160162
Variance annotation uses the `-` and `+` symbols to annotate contravariant and covariant subtyping, respectively. Introducing unary prefix types may lead to some developer confusion.
161163
E.g.
162164

163-
{% highlight scala %}
164-
trait Negate[A]
165-
trait Positive[A]
166-
type unary_-[A] = Negate[A]
167-
type unary_+[A] = Positive[A]
168-
trait Contravariant[B, -A <: -B] //contravariant A subtype upper-bounded by Negate[B]
169-
trait Covariant[B, +A <: +B] //covariant A subtype upper-bounded by Positive[B]
170-
{% endhighlight %}
165+
```scala
166+
trait Negate[A]
167+
trait Positive[A]
168+
type unary_-[A] = Negate[A]
169+
type unary_+[A] = Positive[A]
170+
trait Contravariant[B, -A <: -B] //contravariant A subtype upper-bounded by Negate[B]
171+
trait Covariant[B, +A <: +B] //covariant A subtype upper-bounded by Positive[B]
172+
```
171173

172174
#### Negative Literal Types
173175
Negative literal types are annotated using the `-` symbol. This can lead to the following confusion:
174176

175-
{% highlight scala %}
176-
trait Negate[A]
177-
type unary_-[A] = Negate[A]
178-
trait MyTrait[B]
177+
```scala
178+
trait Negate[A]
179+
type unary_-[A] = Negate[A]
180+
trait MyTrait[B]
179181

180-
type MinusFortyTwo = MyTrait[-42]
181-
type NegateFortyTwo = MyTrait[Negate[42]]
182-
{% endhighlight %}
182+
type MinusFortyTwo = MyTrait[-42]
183+
type NegateFortyTwo = MyTrait[Negate[42]]
184+
```
183185

184186
The above example demonstrates a case of two types `MinusFortyTwo` and `NegateFortyTwo` which are different. They may be equivalent in view (implicit conversion between the two type instances), but they are not equal.
185187

186188
Note: It is not possible to annotate a positive literal type in Scala (checked both in TLS and Dotty):
187189

188-
{% highlight scala %}
189-
val a : 42 = +42 //works
190-
val b : -42 = -42 //works
191-
val c : +42 = 42 //error: ';' expected but integer literal found
192-
{% endhighlight %}
190+
```scala
191+
val a : 42 = +42 //works
192+
val b : -42 = -42 //works
193+
val c : +42 = 42 //error: ';' expected but integer literal found
194+
```
193195

194196
This means that if unary prefix types are added, then `+42` will be a type expansion of `unary_+[42]`.
195197

196-
---
198+
**Related Issues**
199+
* [Dotty Issue #2783](https://github.com/lampepfl/dotty/issues/2783)
200+
* [Typelevel Scala Issue #157](https://github.com/typelevel/scala/issues/157)
201+
202+
Both SIP23 implementation and Dotty's implementation of literal types currently fail compilation when infix types interact with a negative literal type.
203+
```scala
204+
type ~~[A, B]
205+
type good = 2 ~~ 2
206+
type bad = 2 ~~ -2 //Error:(9, 20) ';' expected but integer literal found.
207+
type work_around = 2 ~~ (-2) //works for Typelevel scala, but fails in Dotty
208+
```
209+
It is not yet clear if this is an implementation issue, or if the spec should be changed to allow this as well.
210+
If this is a spec change, then the committee should approve it also.
211+
212+
#### Star `*` infix type interaction with repeated parameters
213+
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 `*`.
214+
Please note that this feature interaction already exists within the current specification.
215+
216+
```scala
217+
trait *[N1, N2]
218+
trait N1
219+
trait N2
220+
def foo(a : N1*N2*) : Unit = {} //repeated parameter of type *[N1, N2]
221+
```
222+
223+
**Related Issues**
224+
* [Dotty Issue #1961](https://github.com/lampepfl/dotty/issues/1961)
225+
197226

198227
## Backward Compatibility
199228
Changing infix type associativity and precedence affects code that uses type operations and conforms to the current specification.

0 commit comments

Comments
 (0)
0