If I have the following (useless) code in Scala 3.3.1:
def f(): Int = 42
def b(): Unit = f()
compiled with -Wvalue-discard I get the correct warning: discarded non-Unit value of type Int on the second line. However, if I try:
def f(): Int = 42
def b(): Unit = f():Unit
this warning is not silenced. I could not find any documentation that this must be so (for Scala 3), but should this not be the expected behaviour? For Scala 2 it is.
It’s a clever solution, however is not an answer to the question (and also too verbose imho). I am curious why issue#7563 did not make it to Scala 3 yet @som-snytt? Projects migrating from Scala 2 to 3 may see a lot of warnings all of a sudden.
An other way of handling this might be something like:
extension (x: Any)
inline def toUnit: Unit = ()
def f(): Int = 42
def b(): Unit = f().toUnit
but i don’t know if this generates any unnecessary code.
The Scala 2 idiom e: Unit was intended as a handy and expressive way to reduce noise. (And that may have been before nowarn was invented.)
Since Scala 3 doesn’t plan to support the ad-hoc mechanism, I plan to delete the advice from messaging. In addition, there are other heuristics around warnings that I plan to put behind a flag.
These gestures at accommodation are mostly a response to the intuition that a warning is too noisy, the people will revolt.
I see the linked PR for Scala 2 includes objections that : Unit is just obscurantism, and value discard is a broken language to begin with.
Note that the value discard warning is redundant with Wnonunit-statement.
Perhaps the pattern match idiom val _ = e is deemed “sufficiently verbose” to support a dubious purpose.
Instead of discarding values, improve API to express that methods are side-effecting (by returning Unit or this.type).
That idiom avoids value discard (which is a standard conversion in the language to adapt the value) but will warn under -Wnonunit-statement (which everyone uses because it’s never a good idea to drop a value on the floor).
Worth adding that I think that when someone said “value discard is evil”, they meant that value discard is only OK if -Wnonunit-statement is not triggered.
I think the missing adaptation is “this means this”, so that methods returning this.type should not have to spell out this:
def f: this.type = status = OK // means { status = OK; this }
Maybe I’ll indulge my predilection for rejection and submit a SIP. (It’s not a coincidence that a SIP is a “submission”.)
But implicitly inserting this is still some “strange magic” that needs to be explained. I think this is much less surpising, although more verbose:
def f: this.type =
status = OK
this
Or if you want to keep your vertical space waste to a minimum:
(note: for those who read this post and wonder what returning this has to do with returning (): Unit, then here is the story: If your api provides methods for updating state your api user might want to chain such updates using dot notation, and that is possible if you get a reference back (instead of returning ()) on which you can continue the chaining:
I’ll take this (so to speak) as my pre-sip pre-rejection.
I think anyone who knows to write this.type would not be surprised by “this insertion”, especially after years of coping with “unit insertion” aka “value discard”.
I don’t mean to argue for it here, but only to note that some features only “enter the consciousness” at a certain “Odersky level”.
We may now understand “Odersky level” not as a level of skill or gameplay but as a level of consciousness.
I’m not pre-rejecting you SIP, just giving my gut reflection hint. Comprehension and consciousness is suported by a delicate tradeoff between conciseness and explicitness. I guess the actual case of enum OderskyLevel does not change that.
After all, there is some compiler magic if it is typed Unit, as then () is inserted, so your SIP could start with the angle that inserting this if typed this.type is an increase in regularity… I’m not sure everybody agrees but it would be interesting to test that assumption balloon with a pre-SIP.