Proposal to remove procedure syntax

Procedure syntax

def f() { ... }

is going to be dropped in Scala 3, where you will need to write the following instead:

def f() = { ... }
def f(): Unit = { ... }

The goal is that Scala 2.13 and 2.14 users will be able to run a Scalafix rewrite to remove procedure syntax from their codebases automatically.

This proposal is going to be discussed in the following SIP meeting. If you feel you have something to share with us with regards to this change, feel free to comment :smile:.

12 Likes

I am sad with this change. I love how Scala is succinct and this will make a language bit worse (from my PoV), unnecessarily verbose heavily used syntax. I was never ā€œconfusedā€ with it when I was learning Scala and I donā€™t see anything bad about clearly signalling with missing equality sign that the function/method doesnā€™t return anything.

If you feel that there is no need for 2 variants of nothing returning functions, I would prefer removal of def f(): Unit = { ... } version.

The verbosity will be felt in particular contexts; for example in scala-swing (where syntax is now already mostly rewritten) the added : Unit = may feel annoying at first. Years ago, I was in favour of procedure syntax, but now I find the benefit of more regular syntax higher.

I work a lot with STM based libraries, so naturally here you have this signature in many many methods:

def something(arg: Int)(implicit tx: Txn): Unit = ...

This is similar to plain mutable style as in Swing. Howeverā€¦ I think here the implicit functions may amortise the verbosity cost of dropping procedure syntax, as you can imagine the above to become something like

def something(arg: Int): TxnU = ...

So this is just a comment not an objection against the removal of procedure syntax.

I have for a long time disliked the change, so for reference: I still dislike it.

In more detail, I find that

  def foo() {
    stuff_that_returns_unit()
  }

provides visual clarity that the method is entirely side-effecting far better than : Unit = does; and I find the danger of def foo = { ... } actually only having side effects rather worrying. The existing way is compact and precise and unambiguous; the alternatives are at least a little worse in all regards (to varying degrees, depending on which alternative).

For me, the feature has paid for itself many times over; Iā€™d rather drop for-comprehensions than procedure syntax.

But I understand that the syntax is more regular if everything has a return value. (People do occasionally stumble over it when learning, but Iā€™ve seen way more stumbling over the shorthand _ in closures, and over the distinction between closures, methods, and functions. These are things we could fix, basically by dropping ā€œconfusingā€ shorthand. But I donā€™t think we want to, because shorthand is helpful.)

For me, the scalafix side of things is basically irrelevant. Fixing my code isnā€™t the hard part. Reading it afterwards is.

2 Likes

For the record, I vehemently oppose this change. Itā€™s a purely gratuitous change that is going to cause all kinds of migration hell for no very good reason. Itā€™s simply too late in the evolution of the language to make this kind of change. Iā€™m all for radical change, and for fixing past design flaws, if the upside is compelling enough. This is squarely not in that camp. Deprecate procedure syntax if you insist, but continue to support it for the foreseeable future.

This would have probably been the less contentious change if I had guessed, but thatā€™s clearly not the case! Thereā€™s nothing much I can add, but in my experience newcomers to Scala have felt quite confused about this syntax and about two ways of doing the same thing. Honestly, I think removing procedure syntax eases readability because people need to have less things in mind when parsing the code visually. Maybe they get used at the end, but itā€™s a price all developers, not only the seasoned ones but the beginners too, need to pay.

You can argue for or against it, but objectively speaking this wonā€™t cause any migration hell. People will have rewrites at their disposal to migrate not only this but other changes too, so this will be automatically fixed for them.

Itā€™s perhaps just because only the one against are talking now :wink:

Iā€™m totally for that change. In my experience (long with scala, thatā€™s more than one decade of it), the cognitive weight of having several syntax for almost the same thing but not exactly is very high, higher than ascetic. Moreover, itā€™s refactoring unfriendly, and forbid some lā€™inter rules (ā€œalways add return type for pub methodā€, for example).

So YES PLEASE, remove it and be done with one less language quirk.

1 Like

Generally, visual parsing is aided by having more distinctions, not fewer. Learnability/memorizability is aided by having fewer, not more.

People may differ on which they find more important.

Iā€™m kind of mystified by the vehemence. I mean, this is the smallest of all the proposed changes, and the most purely mechanical ā€“ itā€™s the one I expect can be 100% covered by Scalafix, with no manual effort required and no obvious edge cases. Itā€™s likely the easiest of all the changes to migrate.

And itā€™s not gratuitous ā€“ Iā€™ve dealt with a fair number of folks, especially newer ones, who have wound up with wildly mysterious bugs because they forgot the = as a pure typo, and lost hours confused because they didnā€™t realize that their function was actually a procedure and was returning Unit. If we didnā€™t have type inference, I might regard this as a minor detail, but as it is, if you use inference heavily (and many people do), procedure syntax is a pretty nasty accidental trap you can fall into.

Iā€™m strongly in favor of this one. Side-effecting functions should be obvious, and an explicit : Unit ascription is much clearer than a quietly missing =. Itā€™s time to make this change, IMOā€¦

13 Likes

Itā€™s perhaps just because only the one against are talking now

@jvican, @fanf has got a point here. Is it possible that in addition to this thread, scala center will release a short survey for the SIP batch, so developers can vote for/against/indifferent of the change?

Not only that, IntelliJ already fixes my code by adding an explicit : Unit =

May I ask how exactly is it refactoring unfriendly? Isnā€™t this just an issue with IDEs not supporting it properly?

Again, why? The linter rule should be fine, if a method doesnā€™t return anything then it canā€™t have a type and passes the rule. What problem is with that?

Voting is out of scope here, weā€™re most interested in the points in favor or against Weā€™ll take into account the inherent bias of people commenting on the tickets, for sure :smile:

1 Like

Honestly, I donā€™t see that as likely to have real value. Unless it was conducted very carefully, the respondents would be quite self-selecting, so youā€™d basically get the noisiest people on both sides voting, but probably not any sort of realistic cross-section of the developer community.

(Itā€™s easy to conduct surveys. Itā€™s hard to conduct surveys that actually tell you anything meaningful.)

2 Likes

I, personally, have no issues with migrating, IDEA can do it already. I worry about the unnecessary increase of verboseness (e.g. when writing a game in Scala, most [70%?] of my methods are purely for side-effects). I donā€™t find argument ā€œit is easy to migrate so we should change the languageā€ convincing. For example, if we would have a Scala-To-Java migration tool, is this a reason to migrate all our code to Java? I donā€™t believe so, I chose Scala because how expressive and terse it is, not because I want to read epic cotton-stuffed tales like in Java.

Never happened to me in described magnitude, even when I was just trying out Scala. If types donā€™t align, I either use an IDE to show me the types (IDEA does this in many instances on its own) or start adding types. I usually find the issue under a minuteā€¦ Shouldnā€™t be rather recommended to beginners to annotate everything with types until they are certain how they are inferred, instead of pushing it out of language because of beginners? Or canā€™t be improved compiler, to show better error messages with tips mentioning possible issues like returning Unit and working with the value?

Note that thereā€™s no need to use Unit returning functions to do side effects, and that weā€™re talking about adding a = between the curly brace and the result type (or parameter list) of the method. I donā€™t think it is a big deal.

2 Likes

Did you get a sense for why they had such trouble with this, as opposed to, say, forgetting yield on a for-comprehension? I have trouble imagining that people who spend hours being confused about this wonā€™t also spend hours being confused about all sorts of other type errors. This is one of the simplest to fix, even if you donā€™t use an IDE, isnā€™t it?

Why do you think that? Every method that returns some non-Unit value has the same form def thing(): Stuff = { ... }. You have to actually read Stuff to know that itā€™s Stuff and not Long or Unit or whatever. You can do the other without even reading anything, purely based on local shape. = is pretty distinctive. You have to find the = anyway in order to read Stuff. If you donā€™t find it, you already know what you need to.

Now, I think thereā€™s an aesthetic advantage in the regularity of all methods having a return value. After all, if you can foo _ and get something, what sense does it make to say that foo has ā€œno return valueā€?

And I suppose those who almost never write purely side-effecting code donā€™t even consider that the = might be missing, so they donā€™t train themselves to look for it.

So I grant that some may find it clearer with the change. But you also should, I think, take me at my word that : Unit = is vastly harder for me to pick out quickly than ) {. If it wasnā€™t actually important to me, I wouldnā€™t say anything. Iā€™ve had plenty of time to notice the difference.

(bold mine)

Please donā€™t ask for that. This makes side-effecting code really hard to pick out, and itā€™s incredibly important to know when something is side-effecting. Itā€™s hard enough to keep side-effects straight even when itā€™s trivial to pick out visually which things are purely side-effecting.

def foo() { .. }

to

def foo(): Unit = { ... }

is okayā€“you can still tell whatā€™s going on.

def foo () = { ... }

is just asking for confusion.

Letā€™s be honest here: weā€™re asking for eight characters with this change, not two. Maybe eight characters isnā€™t too much to ask, but if we want it, letā€™s ask for the actual impact (when maintaining roughly comparable clarity).

I would agree with you, and I wouldnā€™t have suggested this, if he had not said he was working on a game with lots of side effects. If ā€œ70%ā€ of his code is side-effects, there isnā€™t much benefit to annotating every function with Unit as a return typeā€¦

Knowing which is the 70% and which is the 30% can be really important. For instance if bippy builds a new object as opposed to changing the existing one, you want to be really clear on which is which.