-
Notifications
You must be signed in to change notification settings - Fork 1k
SIP-NN - Allow referring to other arguments in default parameters #653
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
Conversation
Quick note - give the SIP a proper title (leave it as SIP-NN until it receives an official number) |
Thanks for submitting this proposal @pathikrit! Will review in a bit. We're reviewing this at the end of this month, I'm preparing the SIP meeting in the next few days and will let you know the exact meeting date soon. |
The other more verbose alternative is by overloading: | ||
|
||
```scala | ||
def substring(s: String, start: Int = 0, end: Int = s.length): String |
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.
This is not working for me, I get <console>:12: error: not found: value s
in the console.
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.
Even if that worked you still can't have two methods with the same type signature.. This needs attention.
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.
Indeed. The trick here is scalac's generation of synthetic methods for the default args.
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.
Oops, a typo. Fixed.
| Jan 12th 2017 | Initial Feedback | | ||
|
||
## Introduction | ||
Currently there is no way to refer to other arguments in the default parameters list: |
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.
Adding a motivation section that showcases why this is useful would be great!
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.
Working on it...
def substring(s: String, start: Int = 0)(end: Int = s.length): String | ||
``` | ||
|
||
However, the above workaround is not always suitable in certain situations. |
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.
Why? Any examples or cases in which the previous transformation cannot be done? I get what you mean here, but explaining it a little bit more could be good for the upcoming review 😄.
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.
Done.
``` | ||
|
||
#### Referring to type members: | ||
The default parameters should be able to refer to type members of other arguments e.g.: |
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.
This section seems orthogonal to the target of this PR (this issue was raised by @olafurpg). I think that the good approach here would be to create another SIP that keeps track of this feature, since I would expect the implementation of this to be different to the one requested. WDYT?
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 don't know much about scalac internals to comment. Maybe it is very similar (then keep 1 SIP) and maybe it isn't (2 SIPs). Can someone more familiar with scalac comment?
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.
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 they're similar in spirit, but I'm sure they're distinct in the implementation.
To avoid a situation where the committee pushes you back a month to go look into and document this, I would keep it in. At the very least they can ask you to split it out - you can then drop it or pursue it as a separate SIP.
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.
Just a first pass on the document requesting some changes to make it clearer. I think the proposed features are great, thank you for the time to write this up!
#### Multiple Implicit Parameters | ||
[Multiple implicit parameters](https://github.com/scala/scala.github.com/pull/520) should also be allowed to refer to one another (left to right): | ||
```scala | ||
def codec[A](data: A)(implicit encoder: Encoder[A])(implicit decoder: Decoder[A] = encoder.reverse) // Legal |
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.
Am I missing something, or this is not legal Scala syntax (multiple implicit parameter lists)?
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 that what @pathikrit means is that this change should also be valid for implicit parameter lists. That would marry well with this SIP.
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.
See the link to the PR
--- | ||
layout: sip | ||
disqus: true | ||
title: SIP-NN - Allow referring to other arguments in default parameters |
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.
"Other arguments" or "previous arguments"?
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.
Maybe previous I guess if other is not feasible (circle dependencies)?
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 would actually write "Allow references to left arguments of the same list in default parameters" if @pathikrit agrees with my previous analysis that doing it for all the arguments is unfeasible. 😄
AFAIK, there are no major languages where referring to parameters declared to the ***right*** is allowed. | ||
|
||
### Discussions | ||
[Scala Lang Forum](https://contributors.scala-lang.org/t/refer-to-previous-argument-in-default-argument-list/215/6) |
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 it would be useful to have an implementation sketch to accompany this SIP. In my opinion, information about why this proposal hasn't been implemented yet and why it is feasible would help the potential discussion during one of the upcoming SIP meetings.
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've been looking into this today and will submit an implementation sketch very soon, along with a design document /cc @pathikrit
The following changes implement a more flexible encoding of default args that allows default implementation to depend on parameters defined on the left (either previous parameters lists or parameters in the same parameter list defined on the left). This encoding breaks binary compatibility so it targets 2.13. It consists of changes in the synthetic method generation of defaults and the call sites of these methods. The implementation of synthetic methods now depends on any parameter defined at the left of a default arg, even if it their body does not use these parameters. This is done so that changing the body of the default args does not change the signature and hence break binary compatibility. More explanation on this implementation can be found in the [SIP proposal](scala/docs.scala-lang#653). WIP: This commit still needs more work. This is what's missing: 1. Reusing the results of default args. Currently, invocations to default methods are inlined at the call-site and a lot of invocations can be reused. Declaring more than one default arg considerably increases bytecode size. My idea to tackle this issue is to save the results of the invocation in vals before the the call-site invocation. Example: ``` // definition def foo(a: Int, b: Int = a, c: Int = a + b): Int = a + b + c // invocation foo(1) ``` will modify the call-site like: ``` foo(1, foo$default$2(1), foo$default$3(1, foo$default$2(1))) ``` As you see, invocations to 1 and `foo$default$2` could be optimized: ``` val a$default = 1 val b$default = foo$default$2(a$default) val c$default = foo$default$3(a$default, b$default) foo(a$default, b$default, c$default) ``` This generates more bytecode for call-sites omitting default arguments, but avoids unnecessary calls that could cause bad performance (especially if more than 2 default args are defined in a method). 2. Generate more tests for the default arguments in class.
@pathikrit I've done some research into this proposal to shed some light into the way scalac encodes default args and how we can improve it according to what you suggest in this proposal. After some experimentation and an implementation prototype, I've decided to document my findings and explore different ways in which this feature can be implemented. Current implementationLet's see a simple example of a method with defaults args: def foo(a: Int, b: Int = 2, c: Int = 3): Int = a + b + c The way scalac encodes this feature is by generating synthetic methods for every def foo(a: Int, b: Int = 2, c: Int = 3): Int = a + b + c
def foo$default$2() = 2
def foo$default$3() = 3 Therefore, when it finds a call site like foo(1, foo$default$2(), foo$default$3()) As you see, the synthetic methods created don't take parameters. This is what What happens if we define the dependencies between curried parameters lists though? def foo(a: Int)(b: Int = a, c: Int = a): Int = a + b + c
def foo$default$2(a: Int) = 2
def foo$default$3(a: Int) = 3 is transformed into: foo(1, foo$default$2(1), foo$default$2(1)) In this case, scalac generates synthetic methods that take Note that these are simplified examples of default args. The current implementation takes care of more details, for example it copies type parameters of How can we implement the changes proposed in this proposal? Exploring other encodingsFirst attemptOur goal with the new encoding is to allow dependencies between parameters defined in the same parameter list, both on the right and on the left. The following method would be allowed: // definition
def foo(a: Int, b: Int = c, c: Int = a): Int = a + b + c
// invocation
foo(a) Let's see if we can find a way to implement it. First, we start with the same encoding used by dependencies in curried parameter lists and modify it to be more flexible. Let's create synthetic methods that define all the parameters that a given default argument could depend on. We'll get the following synthetic methods: def foo$default$2(a: Int, c: Int) = c // default implementation of b
def foo$default$3(a: Int, b: Int) = a // default implementation of c As Second attemptThe previous encoding didn't work because of recursive dependencies in the default args. Why are we generating synthetic methods that could express all the dependencies of an argument? Let's just do it for the parameters that the default arguments actually depend on. def foo$default$2(c: Int) = c // default implementation of b
def foo$default$3(a: Int) = a // default implementation of c Now that we have removed the circular dependency, we can generate code for the call site: foo(a, foo$default$2(foo$default$3(a)), foo$default$3(a)) However, this encoding is worse than we thought. We are encoding the parameter dependency in the signature of the synthetic methods, which means that if we change the implementation of Can we do better? Third and last attemptAt this point, we can only constrain the scope of this proposal to make the default args implementation feasible. Another way to remove the circular dependency that we created in the first encoding attempt is to constrain the parameter dependencies. Instead of allowing any parameter dependency, we will only allow dependencies for:
Therefore, the method we started with is not valid anymore -- // definition
def foo(a: Int, b: Int = a, c: Int = a + b): Int = a + b + c
// invocation
foo(a) With our new limitation, these are the generated synthetic methods: def foo$default$2(a: Int) = a // default implementation of b
def foo$default$3(a: Int, b: Int) = a + b // default implementation of c A similar transformation applies to curried methods Fortunately, our new scheme allow us to:
I think that binary compatibility is utterly important for Scala. I suggest that we don't allow default implementations to depend on arguments defined both on the left and the right, only those on the left. This is closer to what we have right now and it strikes a good tradeoff between usability and power. This encoding will lift a common restriction of the Scala language and enforce a simple rule on library authors that can safely replace default args implementation without thinking in binary compatibility. |
FYI @pathikrit: @sjrd will be the reviewer for this proposal. You can talk to him or me for any doubt you have along the process. He will be the one giving you feedback and leading the discussions in our next meeting. |
@pathikrit I suggest you checkout how this proposal looks on the site, and fix the annotations. I recommend that you build the site locally and view the HTML version of your proposal. The MD to HTML translation is different than what is viewed by an MD editor. See #689 |
No description provided.