Proposal to remove procedure syntax


#1

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:.


First batch of Scala 3 SIPs
#2

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.


#3

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.


#4

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.


#5

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.


#6

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.


#7

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.


#8

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.


#9

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…


#10

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?


#11

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


#12

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?


#13

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:


#14

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.)


#15

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?


#16

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.


#17

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.


#18

(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).


#19

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…


#20

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.