4 ms·
> ... methods should be called with as many sets of parentheses as they are defined with (excluding implicits), and any method call missing parens should be eta
by tekacs 9y ago
> ... methods should be called with as many sets of parentheses as they are defined with (excluding implicits), and any method call missing parens should be eta-expanded into the appropriate function value.
> Notably, removing the "optional parens" thing would not stop you from defining "property" functions that are called without parens; it would just mean you need to define such functions without parens, as is already possible.
> In other languages with first-class functions, like Python, this 'works fine' [quotes mine]
I'm not sure how this behaviour could be seen as 'more consistent'. If methods that have parens but are called with them stripped are eta-expanded but you can still define no-paren methods, you'd still have confusing behaviour where a no-paren method isn't eta-expanded as expected (because it's a property and so 'special').
Rather than more consistent, this behaviour is just 'more like Python'[0], which has this inconsistency too[1]. Essentially the suggestion in the article would mean:
@ def x() = 1
@ val y = x
resXX: () => Int = $...
@ def a = 1
@ val b = a
resYY: Int = 1
It's also worth noting that IntelliJ IDEA (and possibly Eclipse) warn you if you define a no-argument method with Unit return type (since `def action()`s should have parens and `def pure` values needn't).
IDEA will warn you (and offer to correct) if you call a method with too few parens (`def x()` called as `x` will display a very visible warning), so maybe there's some room to believe that this is a 'problem', but the solution involving automatic eta-expansion doesn't seem like a particularly sane one.
Also, calling `getFoo` without parameters is 'nice to have', but calling `name()` as `name` and `color()` as `color` on imported Java APIs both comes across as more natural in Scala (since Java has no properties, they can't be defined as properties upstream) and has allowed quite a few Java APIs to fit in naturally in Scala where they wouldn't have otherwise, in my experience.
[0]: All the eta-expansion things in this post are to 'be more like Python', which makes sense given Haoyi is also behind https://github.com/lampepfl/dotty/issues/2491 https://github.com/lampepfl/dotty/issues/2491 and https://github.com/lihaoyi/Scalite https://github.com/lihaoyi/Scalite and (say) https://github.com/lihaoyi/macropy https://github.com/lihaoyi/macropy.
[1]: Properties in Python also have the underlying design of using descriptors, which are a whole other world of horror.
- lihaoyi 9y agoI don't see the problem with val b = a b: Int = 1 After all, if you wanted to turn it in a function, you can use `() => a` syntax which everyone is already familiar with.
- tekacs 9y agoI don't think it's necessarily wrong, I just think it's inconsistent (all methods are eta-expanded... except properties), just in a slightly different way. Depending on what this did on conversion to Java-land, this could still be confusing for beginners: `int foo() { return 1 }` in Java could either: @ def x = foo resXX: () => Int = $.. // Why was this eta-expanded - it has no parameters?!? @ def y = foo resYY: Int = 1 // This is 'normal'... but it wasn't defined as a property? You could also argue that right now, if you want to turn any method (lacking generics) into a function, you can use the `someFn _` syntax, which in fairness perhaps not everyone is already familiar with. Also, removing parens from an upstream method would change the downstream type, which seems more confusing to me from a 'statically typed language' perspective[0] than up-to-N-parens all having the same type - the rule right now is simple to explain as 'you can drop as many empty parens as you like and the call will stay the same (but IDEA will warn you)'. [0]: In your scheme: @ def foo() = 1 @ val x = foo resXX: () => Int = 1 @ def foo = 1 // Upstream author changes their mind @ val x = foo resXX: Int = 1 // ??? @ val x = () => foo // Have to go around changing to this. ... and vice versa (if an author adds parens, all of your property calls start magically eta-expanding instead)
- lihaoyi 9y agoWhat you say is correct, but I think your mental model isn't the mental model many people work with. One possible way of thinking of things is "methods" and "function values", where you need to eta-expand something to go from "method" to "function value". This is something people get used to when working in Scala for a while. Another way of thinking of things is "things you can call" and "things you cannot call". In this case, it does not make sense to care about "method" or "function value": if it's callable, it should remain callable in the same way as I pass it around, until I call it. If you come from a Python, or JS, or other background, this is the mental model you will have. I personally think the second mental model makes much more sense, and the first is a weird artifact of programming in Scala that you don't see much elsewhere (though I admit C# has a similar dichotomy) I don't find the "people may forget to leave off parentheses" a convincing argument. People may forget to call `.flatten` on nested lists too; doesn't mean we should go flatten everyone's lists for them automatically. Furthermore, if you as an individual wanted to flatten `() => T` into `T` automatically, you can easily do that within your own project with an implicit conversion: implicit def flatten[T](f: () => T): T = f() The same way you could use an implicit to automatically `flatten` nested `List`s, or automatically call `.get` on `Option`s. Both of those are similar to automatically calling functions to avoid "confusion", and something I think most people in the community will agree would be a terrible thing to do