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
partial application of methods ignores call-by-name parameters #302
Comments
Imported From: https://issues.scala-lang.org/browse/SI-302?orig=1 |
@odersky said: |
@odersky said: |
Matt Hellige (hellige) said:
I'm sorry, but I really strongly disagree with this interpretation. I have no problem with your definition of eta expansion, but I disagree with what it means to "evaluate" a by-name parameter. My expectation is that the parameter should be fully evaluated to a thunk, but the thunk should not be entered unless (and until) the name is actually mentioned in the body. Your interpretation has a number of consequences that I find bizarre:
So in short, I agree that you should "evaluate what you can," but I really feel that in this case, you are speculatively evaluating too much. It is not safe to re-order the evaluation of by-name parameters, or even to assume that they will eventually be evaluated at all. My concerns are not primarily theoretical. I am first and foremost worried about the above consequences. If you insist on leaving this as is, would you consider changing the language to allow by-name parameters only in the final parameter list? The current behavior is extremely confusing. |
Matt Hellige (hellige) said: object Test {
def repeat(x: => Unit)(times: Int) = for (_ <- 1 to times) x
def test() {
// prints foo 5 times
repeat(println("foo"))(5)
// prints foo once
val r = repeat(println("foo")) _
// does not print anything
r(5)
}
} If you don't find this behavior at all surprising, maybe you could provide some intuition for why it is correct? |
@odersky said: val t = test(identity(println("never seen")))_ Reassigning to you, because I don't know a better solution. |
Matt Hellige (hellige) said: { val x1 = e1;
...
val xm = em;
(y1:T1,...,yn:Tn) => e'(y1,...,yn)
} with { val x1 = (() => e1);
...
val xml = (() => em);
...
} and in e', replace each ei by xi() rather than xi. This has the effect of explicitly thunking each parameter, and then allowing the usual method call compilation to determine whether or not to unthunk them at call time. If it is possible at expansion-time to inspect the type of the target method and determine which ei are in "by-name position" or whatever you want to call it, then this explicit thunking can be performed only for by-name parameters, eliminating the unnecessary overhead in the common case. Also, if there is some other way of declaring first-class "by-name values," that might be preferred to thunks. IIRC there is no way of doing so, but perhaps internally to the compiler there is some trick available. What do you think? I may try to have a go at implementing this, but it would be my first compiler change, so I won't make any promises. Also, if there is a better place to discuss this, please let me know. |
Matt Hellige (hellige) said: |
@jrudolph said: (test { println("Test" } _)() has the same result as test { println("Test" }() I.e. that you can either apply a function piece-wise partially or directly with all arguments and get the same result. I think the solution as given by Matt is pretty good: at eta-expansion by-name parameters are lifted to vals of a thunk value (of type () => T). However, this is not a valid type to pass as a by-name argument so the application has to change from x1 to x1.apply() as well. If you do this naively the compiler will then (in uncurry) create another redundant thunk out of this application. I therefore propose another change where an expression f.apply() with f being an instance of Function0 in by-name-argument position isn't thunked but instead the application is removed and f passed directly. This should be a safe optimization in all cases because the generated thunk never does more than simple thunking. The hard part of producing proper legalese for the scripture maybe isn't so hard at all because we have a precedent by now. This already is in "§6.6 Function Applications":
We just have to amend the first sentence by "or eta-expansion". Problem solved. If we can reach a consensus that this is the right approach I can put together a pull request with an implementation proposal. |
Robert Gibson (rgibson) said: |
Matt Hellige (hellige) said: |
Consider the following:
This produces:
It should produce just:
scalac -Xprint:typer shows that the definition of t is compiled as:
This eta-expansion incorrectly ignores that the first parameter of test is call-by-name.
The text was updated successfully, but these errors were encountered: