-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Scala Wart: Weak eta-expansion #2570
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
Comments
Another use case:
but not this way:
See this example |
If I remember correctly it used to work like this but it was too error-prone when used on Java methods. In particular
or even better:
In both cases eta-expansion wasn't what you wanted. Perhaps the second one can be ruled-out based on the expected type, or perhaps requiring parenthesis (which I totally agree it's a wart) would help, but people might still make the mistake. So should both these cases be an error? What error should it be? |
I personally don't think your examples are that unexpected; we pass around These are all basically the same problem: that Nevertheless, I think it's strange to have a language feature propagate through every codebase in the world just because people make mistakes when calling a single method |
Maybe the rule can be relaxed to require the |
Whatever you do, with automatic eta-expansion you will always have the special case of methods without parameter lists ( |
That's the plan set in motion by scala/scala#5327 |
I'm leaning against this change. It makes more things that more or less make sense, compile. Isn't that appealing? Sure, but it's a dangerous appeal. The more things compile, the more ways you can screw up:
as @lrytz pointed out in conversation just now, an example of the latter is a refactoring that adds arguments to a method that didn't have them before. now admittedly none of this is conclusive, but I think we should be very cautious about adding yet more ways of writing the same thing to the language, and that's what this change does. |
|
After discussing this with @adriaanm, we are leaning towards the following compromise proposal for handling references to unapplied methods
|
Interesting! I love the idea of having one less meaning for |
👍 I'd personally prefer to eta-expand I suppose EDIT: I guess the whole eta-expand-nullary-methods thing was so controversial everybody forgot about the earlier case: what do people think about being able to expand def repeat(s: String, i: Int) = s * i
val partial = repeat("hello", _) without an expected type? |
Yes, a reference |
Martin and I have both been working on an implementation for this :-) |
See #2691. |
No. methods with implicit parameter lists always get applied to implicit arguments. |
But what about |
It becomes |
Implements another part of scala#2570
Implements another part of scala#2570
Implements another part of scala#2570
Follows the scheme outlined in scala#2570. The reason not to flag bad calls to methods compiled from Scala 2 is that some Scala 2 libraries are not yet in a form where the parentheses are as they should be. For instance def isWhole() in some of the numeric libraries should not have the ().
Implements another part of scala#2570
Follows the scheme outlined in scala#2570. The reason not to flag bad calls to methods compiled from Scala 2 is that some Scala 2 libraries are not yet in a form where the parentheses are as they should be. For instance def isWhole() in some of the numeric libraries should not have the ().
Why not just do as java and reference functions with the double colon operator or something similar and anytime you want the function to be executed you just give it the name. For example, |
Implements another part of scala#2570
Follows the scheme outlined in scala#2570. The reason not to flag bad calls to methods compiled from Scala 2 is that some Scala 2 libraries are not yet in a form where the parentheses are as they should be. For instance def isWhole() in some of the numeric libraries should not have the ().
Implements another part of scala#2570
Follows the scheme outlined in scala#2570. The reason not to flag bad calls to methods compiled from Scala 2 is that some Scala 2 libraries are not yet in a form where the parentheses are as they should be. For instance def isWhole() in some of the numeric libraries should not have the ().
The linked diff appears to also enforce the correct-number-of-empty-parens; should #2571 be closed as well? |
Yes indeed. |
Opening this issue, as suggested by Martin, to provide a place to discuss the individual warts brought up in the blog post Warts of the Scala Programming Language and the possibility of mitigating/fixing them in Dotty (and perhaps later in Scala 2.x). These are based on Scala 2.x behavior, which I understand Dotty follows closely, apologies in advance if it has already been fixed
Scala maintains a distinction between "functions" and "methods": in general,
methods are things you call on an object, whereas functions are objects
themselves. However, since they're so similar ("things you can call"), it gives
you a way to easily wrap a method in a function object called "eta expansion"
Above, we use the underscore
_
to assignrepeat _
to a valuefunc
, whichis then a function object we can call. This can happen automatically, without the
_
, based on the "expected type" of the place the method is being used. Forexample, if we expect
func
to be a(String, Int) => String
, we can assignrepeat
to it without the_
:Or by stubbing out the arguments with
_
individually:This works, but has a bunch of annoying limitations. Firstly, even though you
can fully convert the method
repeat
into a(String, Int) => String
valueusing
_
, you cannot partially convert it:Unless you know the the "expected type" of
func
, in which case you canpartially convert it:
Or you provide the type to the partially-applied-function-argument
_
manually:This is a bit strange to me. If I can easily convert the entire
repeat
methodinto a function without specifying any types, why can I not convert it into a
function if I already know one of the arguments? After all, I have provided
strictly more information in the
repeat("hello", _)
case than I have in therepeat(_, _)
case, and yet somehow type inference got worse!Furthermore, there's a more fundamental issue: if I know that
repeat
is amethod that takes two arguments, why can't I just do this?
After all, since the compiler already knows that
repeat
is a method, and thatit doesn't have it's arguments provided, why not convert it for me? Why force me
to go through the
_
or(_, _)
dance, or why ask me to provide an expected typefor
func
if it already knows the type ofrepeat
?In other languages with first-class functions, like Python, this works fine:
The lack of automatic eta-expansion results in people writing weird code to work
around it, such as this example from ScalaMock:
Here, the weird
(foo _)
dance is something that they have to do purelybecause of this restriction in eta-expansion.
While I'm sure there are good implementation-reasons why this doesn't work, I
don't see any reason this shouldn't work from a language-semantics point of
view. From a user's point of view, methods and functions are just "things you
call", and Scala is generally successful and not asking you to think about the
distinction between them.
However, in cases like this, I think there isn't a good reason the compiler
shouldn't try a bit harder to figure out what I want before giving up and
asking me to pepper
_
s or expected types all over the place. The compileralready has all the information it needs - after all, it works if you put
an
_
after the method - and it just needs to use that information whenthe
_
isn't present.The text was updated successfully, but these errors were encountered: