Why not drop empty argument lists instead of auto-application?

The idea here would be to disallow () as redundant at both definition site and use site of methods.

The only justification for keeping it that I found here is

Scala encourages to define such “property” methods without a () parameter list whereas side-effecting methods should be defined with it

Other then the above, adding () arg lists appear to be redundant. So long as this is only encouraged and not enforced I don’t see it giving us much, I’d certainly feel that without a guarantee from the compiler I can’t trust it anyway, so () would remain just line noise.

This could be enforced for java methods too: enforce myVal.toString, would this cause any issues? If yes, this part can be omitted.

Also, an effect system won’t make it into Scala 3 but AFAICT is an area of interest that will likely make it in sooner or later, that should be able to give us guarantees wrt. side effects making this encouraged style even more redundant, if there is such a thing (but I find my point valid even if no such effect system will ever be part of the language)

I use () to annotate impure methods. As you say, nothing is enforced by the compiler, but following this convention provides me value and is better than nothing. Obviously I’d love if the compiler itself were able to enforce purity/impurity but until that happens I prefer to have this rather than nothing at all.

Btw, returning an impure type make a method impure. An example is anything that returns an Iterator. If one were to enforce anything in this area to Java methods, one would have to know what Java methods and types are pure and immutable respectively.

5 Likes

This is actually an interesting proposal.

Personally, I would have a hard time adapting to call sites that only call a side-effecting zero-arg method if there were no ():

foo.delete

looks too much like accessing a field/property named delete for my brain to register that this actually has side-effects.

But intuition set aside, I believe the biggest objective issue is that it won’t play well with the combination of default parameters + curried argument lists. Consider the following definition and call site:

def foo(arg1: Int = 0)(arg2: Int = 1): Int = arg1

foo(5)

Is the last expression supposed to expand to foo(5)(1) or foo(0)(5). You’ll never know. And if calling a 0-arg method is always supposed to be done without (), you cannot use foo(5)() or foo()(5) for disambiguation. It would look too alien.

For that reason I do not believe this solution is practical in Scala.

9 Likes

I find this situation to be similar to the new status of new, which is allowed when disambiguation is needed. IIUC this means you can still use new everywhere if you want to. That would map to keeping the status quo wrt. ().

To keep things more regular, which is an explicit goal of Scala 3 as far as I know, you could prohibit both new and () when not needed and require it when there is a need for disambiguation. Not sure if this is technically doable in case of new, didn’t look into that. It should be doable in case of ()

new has semantics though—it ensures that the returned object has a distinct identity.

2 Likes

It would also break java interop. Or interop with any JVM language that does not follow the uniform access principle.

foo and foo() can be radically different semantically in that case and must be distinguished from each other.

1 Like

If that’s the case Java methods could be left out as per my initial post, they already receive special treatment wrt. dropped auto-application

I don’t think Java interop would be broken. We already treat all () methods in Java as being callable without the ().

However, now that you mention it, I am reminded that it would be devastating to JavaScript interop in Scala.js. Because in Scala.js, whether a method is declared with () or without makes a great deal of difference in the run-time semantics.

4 Likes

Worth adding that parens also matter on the eta-expansion side, non-nullary versus nullary versus nilary.

Starting dotty REPL...
scala> def f(i: Int) = i + 1
def f(i: Int): Int

scala> f
val res0: Int => Int = Lambda$1264/0x0000000100e08040@e3ed455

scala> def m() = 42
def m(): Int

scala> m
1 |m
  |^
  |method m must be called with () argument

scala> m : (() => Int)
val res1: () => Int = Lambda$1316/0x0000000100e89040@37e55819

scala> def n = 42
def n: Int

scala> n : (() => Int)
1 |n : (() => Int)
  |^
  |Found:    Int
  |Required: () => Int

I see Scala 2 allows n _ but not Scala 3. I need a scorecard.

2 Likes