Skip to content

SIP-NN - Match infix & prefix types to meet expression rules #674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 13, 2017
187 changes: 187 additions & 0 deletions sips/pending/_posts/2017-02-07-make-types-behave-like-expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
layout: sip
disqus: true
title: SIP-NN - Make types behave like expressions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current title is too general, it's not clear what is this sip about if you only read the title.
May I suggest:
Types have precedence and associativity rules as expressions

Copy link
Contributor Author

@soronpo soronpo Feb 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposal is more than just infix types.
Match infix & prefix types to meet expression rules is better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @DarkDimius that a better name is desirable -- this one makes you wonder: "Behave like expression in regard to what?". I think Dmitry's suggestion is good, I would just add 'Make' at the beginning of the sentence: Make types have precedence and associativity rules as expressions. I'm not sure whether 'like' would be more appropriate in that case, maybe you can choose @soronpo, my non-native English is not that good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that's way better @soronpo, let's go with that name!

---

**By: Oron Port**

## History

| Date | Version |
|---------------|--------------------------|
| Feb 7th 2017 | Initial Draft |
| Feb 9th 2017 | Updates from feedback |

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.

---
## Introduction
Currently scala allows symbol operators (`-`, `*`, `~~>`, etc.) for both type names and definition names.
Unfortunately, there is a 'surprise' element since the two differ in behaviour:

* **Infix operator precedence and associativity**:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make these subtitles instead of bold text?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will gladly do so. What do you mean by 'subtitles'? I'm not that familiar with MD.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like '###' would be enough 😄.

Infix types are 'mostly' left-associative,
while 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you roughly explain what the current rules for infix operations are? A little bit of context will help the reader understand what you're proposing.

Copy link
Contributor Author

@soronpo soronpo Feb 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Gave a short description and provided an example. I can also place a screenshot of the entire section.


**Example**:
```scala
object InfixExpressionPrecedence {
case class Nummy(expand : String) {
def + (that : Nummy) : Nummy = Nummy(s"Plus[$this,$that]")
def / (that : Nummy) : Nummy = Nummy(s"Div[$this,$that]")
}
object N1 extends Nummy("N1")
object N2 extends Nummy("N2")
object N3 extends Nummy("N3")
object N4 extends Nummy("N4")
//Both expand to Plus[Plus[N1,Div[N2,N3]],N4]
assert((N1 + N2 / N3 + N4).expand == (N1 + (N2 / N3) + N4).expand)
}
object InfixTypePrecedence {
trait Plus[N1, N2]
trait Div[N1, N2]
type +[N1, N2] = Plus[N1, N2]
type /[N1, N2] = Div[N1, N2]
trait N1
trait N2
trait N3
trait N4
//Error!
//Left expands to Plus[Plus[N1,Div[N2,N3]],N4] (Surprising)
//Right expands to Plus[Div[Plus[N1,N2],N3],N4]
implicitly[(N1 + N2 / N3 + N4) =:= (N1 + (N2 / N3) + N4)]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "Left" and "Right" are mixed up here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed that in #855

}
```

* **Prefix operators bracketless unary use**: 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.
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.


**Example**:
```scala
object PrefixExpression {
case class Nummy(expand : String) {
def unary_- : Nummy = Nummy(s"-$this")
def unary_~ : Nummy = Nummy(s"~$this")
def unary_! : Nummy = Nummy(s"!$this")
def unary_+ : Nummy = Nummy(s"+$this")
}
object N extends Nummy("N")
val n1 = -N
val n2 = ~N
val n3 = !N
val n4 = +N
}
object NonExistingPrefixTypes {
trait unary_-[A]
trait unary_~[A]
trait unary_![A]
trait unary_+[A]
trait N
type N1 = -N //Not working
type N2 = ~N //Not working
type N3 = !N //Not working
type N4 = +N //Not working
}
```

---
## Proposal
The proposal is split into two; type infix precedence, and prefix unary types. Note to the SIP committee: It might be better to vote on the two parts separately.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the two features that you propose are related. However, I wonder if it would be better that you submit two different proposals. As the SIP Committee Lead, I would suggest that we do it this way because it's conceptually simpler -- if one is accepted and the other one is not, we'll be able to mark one of the documents in our official website as approved. This way, it wouldn't be neither approved nor rejected and it would be confusing to keep track of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to do the following? Not to separate them, but if the committee approves one but not the other then I will split the SIP into two to allow marking them separately.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do it this way, yes. We'll see how this experiment goes, we haven't actually had this situation happened yet.

Copy link
Contributor Author

@soronpo soronpo Feb 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well you did approve meta while requesting it to be split into two SIPs. In that case, though, the condition was that they both were still coupled together. In this SIP I want both parts to be approved, but I understand that changing infix precedence is much more likely to be approved than adding prefix types.


### Proposal, Part 1: Infix type precedence & associativity
Make infix types conform to the same precedence and associativity traits as expression operations.
### Proposal, Part 2: Prefix unary types
Add prefix types, exactly as specified for prefix expression.


---
## Motivation
The general motivation is developers expect terms and types to behave equally regarding operation precedence and availability of unary types.

### Motivating examples
#### Dotty infix type similarity
Dotty infix type associativity and precedence seem to act the same as expressions.
No documentation available to prove this, but the infix example above works perfectly in dotty.

Dotty has no prefix types, same as Scalac.

#### Singleton-ops library example
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.
For example:
```scala
import singleton.ops._

val four1 : 4 = implicitly[2 + 2]
val four2 : 2 + 2 = 4
val four3 : 1 + 3 = implicitly[2 + 2]

class MyVec[L] {
def doubleSize = new MyVec[2 * L]
def nSize[N] = new MyVec[N * L]
}
object MyVec {
implicit def apply[L](implicit check : Require[L > 0]) : MyVec[L] = new MyVec[L]()
}
val myVec : MyVec[10] = MyVec[4 + 1].doubleSize
val myBadVec = MyVec[-1] //fails compilation, as required
```
We currently loose some of the intuitive appeal due to the precedence issue:
```scala
val works : 1 + (2 * 3) + 4 = 11
val fails : 1 + 2 * 3 + 4 = 11 //left associative:(((1+2)*3)+4))) = 13
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand more on this motivation? Also, can you rename the title Singleton ops library and use instead Motivation? I think this use case is valid, but I'm a little bit worried that it can be read as "we need this functionality only for this library". I think there's a more general, overarching philosophical purpose to the change you propose, since essentially you're trying to make terms and types behave equally regarding operation precedence. For a functional language like Scala, that argument would read better 😄.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you rename the title Singleton ops library and use instead Motivation?

It's a subsection within the Motivation section. Is that not enough?

I think there's a more general, overarching philosophical purpose to the change you propose, since essentially you're trying to make terms and types behave equally regarding operation precedence. For a functional language like Scala, that argument would read better

OK, will modify text to reflect that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good enough.


#### Developer issues example
The following stackoverflow question demonstrate developers are 'surprised' by the difference in infix precedence, expecting infix type precedence to act the same as expression operations.
http://stackoverflow.com/questions/23333882/scala-infix-type-aliasing-for-2-type-parameters



## Interactions with other language features

#### Variance Annotation
Variance annotation uses the `-` and `+` symbols to annotate contravariant and covariant subtyping, respectively. Introducing unary prefix types may lead to some developer confusion.
E.g.
```scala
trait Negate[A]
trait Positive[A]
type unary_-[A] = Negate[A]
type unary_+[A] = Positive[A]
trait Contravariant[B, -A <: -B] //contravariant A subtype upper-bounded by Negate[B]
trait Covariant[B, +A <: +B] //covariant A subtype upper-bounded by Positive[B]
```

#### Negative Literal Types
Negative literal types are annotated using the `-` symbol. This can lead to the following confusion:
```scala
trait Negate[A]
type unary_-[A] = Negate[A]
trait MyTrait[B]

type MinusFortyTwo = MyTrait[-42]
type NegateFortyTwo = MyTrait[Negate[42]]
```
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.

Note: It is not possible to annotate a positive literal type in Scala (checked both in TLS and Dotty):
```scala
val a : 42 = +42 //works
val b : -42 = -42 //works
val c : +42 = 42 //error: ';' expected but integer literal found
```
This means that if unary prefix types are added, then `+42` will be a type expansion of `unary_+[42]`.

---

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

@jvican jvican Feb 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FTR, it would be very interesting to see how often type alias precedence is used. If you really care about this (and want to enrich the proposal with high-quality data that helps the Committee decide), you can explore it using Scala Meta. Run it it common Scala projects and give us some stats (note that this is optional 😉).


---

### Bibliography
[Scala Contributors](https://contributors.scala-lang.org/t/sip-nn-make-infix-type-alias-precedence-like-expression-operator-precedence/471)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put this into "Bibliography"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

[scala-sips](https://groups.google.com/forum/#!topic/scala-sips/ARVf1RLDw9U)