-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Changes from 7 commits
c75ad08
5eb415c
1d6b25c
4a7f32d
4638248
2659193
6719504
d219232
84d5cc5
7750b93
57f8d0a
9d24b3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
--- | ||
layout: sip | ||
disqus: true | ||
title: SIP-NN - Make types behave like expressions | ||
--- | ||
|
||
**By: Oron Port** | ||
|
||
## History | ||
|
||
| Date | Version | | ||
|---------------|------------------| | ||
| Feb 7th 2017 | Initial Draft | | ||
|
||
--- | ||
## 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**: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we make these subtitles instead of bold text? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 operations are more intuitive with different precedence weights. | ||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"Prod[$this,$that]") | ||
override def toString : String = expand | ||
} | ||
object N1 extends Nummy("N1") | ||
object N2 extends Nummy("N2") | ||
object N3 extends Nummy("N3") | ||
object N4 extends Nummy("N4") | ||
val result_expected = N1 + N2 * N3 + N4 | ||
//result_expected.expand is Plus[Plus[N1,Prod[N2,N3]],N4] | ||
} | ||
object InfixTypePrecedence { | ||
trait Plus[N1, N2] | ||
trait Prod[N1, N2] | ||
type +[N1, N2] = Plus[N1, N2] | ||
type *[N1, N2] = Prod[N1, N2] | ||
trait N1 | ||
trait N2 | ||
trait N3 | ||
trait N4 | ||
type Result_Surprise = N1 + N2 * N3 + N4 | ||
//Result_Surprise expands to Plus[Prod[Plus[N1,N2],N3],N4] | ||
type Result_Expected = N1 + (N2 * N3) + N4 | ||
//Result_Expected expands to Plus[Plus[N1,Prod[N2,N3]],N4] | ||
} | ||
``` | ||
|
||
* **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. | ||
(Author's note: Not crucial as infix precedence, but good for completeness) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you remove author's notes? 😄. The document will be exposed to all the community and add a little bit of overhead to the document. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving notes to commit description. |
||
|
||
**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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
(Author's note: I can copy-paste the specification and modify it, if it so required) | ||
### Proposal, Part 2: Prefix unary types | ||
Add prefix types, exactly as specified for prefix expression. | ||
(Author's note: I can copy-paste the specification and modify it, if it so required) | ||
|
||
--- | ||
|
||
## Motivating examples | ||
#### Dotty infix type similarity | ||
Dotty infix type associativity and precedence seem to be the same as expressions (Author's note: I have seen no documentation of this, but checked the implementation for a simple example `implicitly[(N1 + (N2 / N3) + N4) =:= (N1 + N2 / N3 + N4)]`). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you try if the code snippet used as an example at the beginning works in Dotty? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I have. I verified it works. Maybe it's not clear from the context. I had to modify to example to a |
||
Dotty has no prefix types. | ||
|
||
#### Singleton-ops library | ||
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 | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's a subsection within the Motivation section. Is that not enough?
OK, will modify text to reflect that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, good enough. |
||
|
||
#### Developer issues | ||
The following stackoverflow question demonstrate developers are 'surprised' by the difference in infix precedence. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate a little bit on what they are surprised? A little bit of context here would be super useful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
http://stackoverflow.com/questions/23333882/scala-infix-type-aliasing-for-2-type-parameters | ||
|
||
|
||
|
||
--- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these dashes intended? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I like to keep things separate, but since you asked, removed. |
||
|
||
## Interactions with other language features | ||
|
||
#### Variance Annotation | ||
Variance annotation uses the `-` and `+` symbols to annotate contravariant and covariant subtyping, respectively. Introducing unary prefix types might lead to some 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] | ||
``` | ||
(Author's note: it seem very unlikely that such feature interaction will occur) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you explored this feature interaction? Perhaps you can have a look at the grammar rules and "prove" that it's not possible for it to happen? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feature interaction does not collide. It may be confusing for a developer to see |
||
|
||
#### 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]`. | ||
|
||
#### Scala meta | ||
Open question how this SIP affects `scala-meta`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scalameta's parser would need to be changed, but I think this section can go away. The trailing commas SIP doesn't mention Scala Meta (IIRC) and it's a detail not relevant to discuss the proposal in depth (any change to parser rules needs changes in tooling). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. Will remove the 'Scala meta' subsection. |
||
|
||
--- | ||
|
||
## Backward Compatibility | ||
Changing infix type associativity and precedence affects code that uses type operations and conforms to the current specification. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 😉). |
||
(Author's note: I don't know if providing a flag to select the precedence is good or not. IMHO, it is better to create a tool that adds brackets to convert code to the old associativity.) | ||
|
||
--- | ||
|
||
### Extended proposal alternative | ||
It is possible to extend this proposal and allow the developer to annotate the expected associativity and precedence per operation. | ||
(Author's note: I personally don't like this, but if such a solution is better for the community, then I will gladly modify this SIP to reflect that.) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is a good idea as it adds a significant layer of complexity on the language and forces Scala developers to inspect the library-defined operations to know what's their precedence and how they can be used. I would propose this is not included in this document and, if someone wants this feature, that a new proposal is submitted! |
||
See the following [Typelevel Scala issue](https://github.com/typelevel/scala/issues/69) for the suggestion. | ||
|
||
### Other languages | ||
Would love some help to complete what happens in different programming languages. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we put this in the description instead of the document? 😄 |
||
|
||
### Discussions | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make more explicit that any comment on this proposal should go to the Discourse thread? Something like: "If you're interested in discussing this proposal, head over to this Scala Contributors thread and let me know what you think". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added at the top. |
||
[Scala Contributors](https://contributors.scala-lang.org/t/sip-nn-make-infix-type-alias-precedence-like-expression-operator-precedence/471) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we put this into "Bibliography"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
[scala-sips](https://groups.google.com/forum/#!topic/scala-sips/ARVf1RLDw9U) |
There was a problem hiding this comment.
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
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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.There was a problem hiding this comment.
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!