Paramterless functions with and without parentheses different!

I can define

def answerToAllQuestions() : int =
  42

and call it val answer = answerToAllQuestions()

Then I can define

def answerToAllQuestions : int =
  42

and I cannot call it the same way:

val answer = answerToAllQuestions()

gives me “Syntax Error: method answerToAllQuestions does not take parameters”.

Why’s that? answerToAllQuestions() shows indeed that there are no parameters…

Although I do understand that the text of the definitions is different, I think the type signatures of both functions are the same and should make the use of the functions indistinguishable…

Bonus question: Which of the two ways to write the defintion of a paramterless functions is “better practice”?

1 Like

In Scala, methods without parentheses and with empty parentheses are treated as separate. In Scala 2, methods defined with empty parentheses were still allowed to be called without them, while in Scala 3 the callsite must always match the definition (currently only enforced, when the definition is also Scala 3 code).

The recommended usage is to give side-effecting methods parentheses, while keeping functions free from side effects without them, so that they can be changed into a val without causing source changes. But note that this usage is a convention and not enforced by the compiler in any way, so missing parentheses are not a guarantee for purity.

See here for more background: Dropped: Auto-Application

5 Likes

Wow, great answer! Interesting details. Thanks a lot!

Good to know that methods without parentheses are for pure functions (like rangeOfdata : Range) while methods with parentheses are for side-effecting functions (like printRangeOfdata() : Unit.

I’m still struggling with that and can’t make up my mind in some situations. For instance, iterator or iterator()? currentTimeMillis or currentTimeMillis()? future.get or future.get()? They cannot possibly be val/var but they also don’t quite have a side effect (though blocking a thread can count as one). And even if I convince myself that iterator() makes more sense than iterator, I still like iterator.map(...) better than iterator().map(...). I’m still looking for to a consistent rule to follow in my own code.

1 Like

One thing you can do is consider whether or not the function is pure. If it use, then no parenthesis. Any function that does IO is not. Any function that calls the OS, such as currentTimeMillis, is not. For the same reason, anything that deals with external resources such as files and threads I would also consider a case for using parenthesis.

For the iterator used on “pure” data I would consider using without parenthesis - every time you create an iterator and use it, it should produce the same results.

Just an opinion.

3 Likes

I’d argue that #iterator() is not referentially transparent - given val iter = data.iterator(), you cannot freely swap iter for data.iterator() in subsequent code, so it should be considered impure.

5 Likes

Yeah, exactly – thinking in terms of referential transparency tends to produce clearer answers than “side-effects” does, because it isn’t always intuitively clear what constitutes a side-effect.

(For those who aren’t familiar with it, it’s worth looking up referential transparency. It looks like a minor detail at first, but turns out to be central to serious functional programming, and FP ecosystems like Typelevel and ZIO. Once you internalize it, it helps clarify how to use those ecosystems, as well as questions like this.)

1 Like

But iterator is without parentheses in the standard collections, isn’t it?

What about size on a mutable collection? Not pure, not referentially transparent, but still without parentheses in the standard library.

And although I will use parentheses for future.get(), can’t we argue that it is functionally interchangeable with x defined as val x = future.get()? (I’m thinking of the blocking aspect of it, not exceptions; a bit of a stretch, I admit.)

(Somewhat related, another one I struggled with in the past is new Foo versus new Foo(). Fortunately, Scala 3 solved that for me and I now just write Foo().)

Yeah, well – much of stdlib is very old, in some cases practically ancient. It’s not always the ideal guide to best practice.

I’m confused – what do you mean by future.get()? I don’t think I know what method you’re talking about.

Yes, consistent with the official convention, which only talks about side effects, probably with the intent to maximize applicability of the uniform access principle. Purity would be an alternative, stricter (and still underspecified) criterion.

Just found this discussion specific to #iterator().

1 Like

It’s a method of java.util.concurrent.Future (I deal with Java/Scala combinations a lot). I use parentheses, but get() always produces the same value (barring exceptions), so technically, one could argue that the referential transparency is there.

Things like iterator bother me more. I agree that it should be iterator(), but that doesn’t look as nice in chains, and it’s inconsistent with the standard library, however old it is.

I also had to deal with a function getTime(), where the parentheses are justified, but the silly IDE flags it as unnecessary parentheses on a “getter” :smirk:.

Correct, and it was intentionally kept that way even when the collections were reworked for Scala 2.13 circa 2018. The design discussion on that is at Remove empty parameter list from `iterator()` · Issue #520 · scala/collection-strawman · GitHub

2 Likes

Ahhh – okay, sure. I haven’t touched Java’s Future in years: last time I did, I remember it causing a lot of challenges to get it working correctly in the context of a pure Scala application.

Yeah, that’s an interesting edge case: it may well preserve referential transparency (although I’d need to think that through), at the cost of thread-blocking. I could see arguing for paren-less in that case, but that unbounded wait is a complication I don’t usually think about in this context.

Interesting discussion! I guess there’s no rigorously clean answer.