Description
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 lets you leave off empty-parens lists when calling functions. This
looks kind of cute when calling getters:
@ def getFoo() = 1337
defined function foo
@ getFoo()
res8: Int = 1337
@ getFoo
res9: Int = 1337
However, it doesn't really make sense when you consider how this works in
most other languages, such as Python:
>>> def getFoo():
... return 1337
...
>>> getFoo()
1337
>>> func = getFoo
>>> func()
1337
After all, if getFoo()
is a Int
, why shouldn't getFoo
without the
parens be a () => Int
? After all, calling a () => Int
with parens
give you an Int
. However, in Scala methods are "special", as shown above,
and methods with empty parens lists are treated even more specially.
Furthermore, this feature really doesn't make sense when you start pushing it:
@ def bar()()()()() = 2
defined function bar
@ bar
res11: Int = 2
@ bar()
res12: Int = 2
@ bar()()
res13: Int = 2
@ bar()()()
res14: Int = 2
@ bar()()()()
res15: Int = 2
@ bar()()()()()
res16: Int = 2
@ bar()()()()()()
cmd17.sc:1: Int does not take parameters
val res17 = bar()()()()()()
^
Compilation Failed
Is this really the behavior we expect in a statically-typed language, that you
can call this method with any number of argument lists 0 < n <= 5
and
it'll do the same thing regardless? What on earth is the type of bar
? The
Scala community likes to think that it's "definition-side variance" is better
than Java's "use-site variance", but here we have Scala providing
definition-site parens where every caller of bar
can pick and choose how many
parens they want to pass.
I think the solution to this is clear: methods should be called with as many
sets of parentheses as they are defined with (excluding implicits). Any
method call missing parens should be eta-expanded into the appropriate
function value.
Concretely, that means that given these two functions:
object thing{
def head: T = ???
def next(): T = ???
}
They currently behave like this:
val first1: T = thing.head // Works!
val first2: T = thing.head() // Compile Error: T is not a function and cannot be called
val first3: T = thing.next // Works!
val first4: T = thing.next() // Works!
And will there-after behave like this:
val first1: T = thing.head // Works!
val first2: T = thing.head() // Compile Error: T is not a function and cannot be called
val first3: T = thing.next // Compile Error: found () => T, expected T
val first4: T = thing.next() // Works!
Notably, this does not take away the ability to control how many empty-parens
a function is called with; rather, it shifts that decision from the user of a
function to the author of a function. Since the author of a function already
decides everything else about it (It's name, arguments, return type,
implementation, ...) giving the author the decision over empty-parens would
not be unprecedented.
No-parens "property" functions would still be possible, the author of the
function would just need to define it without parens, as is already
possible:
@ def baz = 3
defined function baz
@ baz
res11: Int = 3
@ baz()
cmd12.sc:1: Int does not take parameters
val res12 = baz()
^
Compilation Failed
The only reason I've heard for this feature is to "let you call Java getFoo
methods without the parens", which seems like an exceedingly weak justification
for a language feature that so thoroughly breaks the expectations of a
statically-typed language. If that was the problem, one option would be to
allow use-site optional empty-parentheses only at Java call-sites or Scala
call-sites with a particular annotation (@optionalParens def foo = ...
?).
This would limit the scope of this behavior to a mild Java-interop quirk
(one of many), rather than a wart affecting the core of the Scala programming
language
PostScript:
One reason in support of the current behavior I have encountered repeatedly in online forums is that this allows you to "fix" library APIs so they better match the pure/non-pure semantics of their functions: so if a library author writes a pure function with parentheses, you can "fix" the API and call it without parentheses. That way your code looks "idiomatic" and "correct" w.r.t. purity and parens even if upstream authors mess up.
I don't really think this is a good justification, for the exact same logic can be used to justify unlimited monkey-patching of upstream code, which I think most will agree does not make sense. Reductio ad absurdum