The syntax construct do while is no longer supported. Instead, it is recommended to use the equivalent while loop. For instance, instead of
do
i += 1
while (f(i) == 0)
one writes
while
i += 1
f(i) == 0
do ()
That’s great and all - I’ve been doing this for some time before since the while-construct allows definitions in the loop preceding the condition to be used in the final conditional, whereas do-while does not. However, could we just allow the while without the silly do () at the end?
I also think it’s moderately clearer to use {} than (), since {} is clearly an empty block while () is the unit value, placed in a position that doesn’t return a value so it’s silently discarded.
Functionally, they’re equivalent. Conceptually, the logic is “no code block”, though.
In general, one might want the test anywhere.
boundary:
while true do
preamble
if !test then boundary.break()
postamble
This is the general form of a loop that has an arbitrary entry point. But we don’t need boundary/break to express it:
while
preamble
test
do
postamble
I prefer to see all the parts, even if they’re empty, especially since little tweaks can convert one to the other.
That is backward. The empty braces require insertion of parens. Scala has no empty blocks.
scala> def f = {}
[[syntax trees at end of typer]] // rs$line$2
package <empty> {
final lazy module val rs$line$2: rs$line$2 = new rs$line$2()
final module class rs$line$2() extends Object() { this: rs$line$2.type =>
def f: Unit =
{
()
}
}
}
def f: Unit
scala> def g = ()
[[syntax trees at end of typer]] // rs$line$3
package <empty> {
final lazy module val rs$line$3: rs$line$3 = new rs$line$3()
final module class rs$line$3() extends Object() { this: rs$line$3.type =>
def g: Unit = ()
}
}
def g: Unit
scala> while 1 > 2 do {}
[[syntax trees at end of typer]] // rs$line$4
package <empty> {
final lazy module val rs$line$4: rs$line$4 = new rs$line$4()
final module class rs$line$4() extends Object() { this: rs$line$4.type =>
val res0: Unit =
while 1 > 2 do
{
()
}
}
}
scala> while 1 > 2 do ()
[[syntax trees at end of typer]] // rs$line$5
package <empty> {
final lazy module val rs$line$5: rs$line$5 = new rs$line$5()
final module class rs$line$5() extends Object() { this: rs$line$5.type =>
val res0: Unit = while 1 > 2 do ()
}
}
The idea that leaving out the do clause means the same as do () does have precedence elsewhere in the language, e.g. leaving out the finally and else clauses mean roughly the same as finally () and else (). I don’t think there’s that much precedence for leaving out the trailing clause of a control flow structure means the earlier clause is removed, so hopefully the proposed behaviour wouldn’t be too confusing for people
That is a fair point. However, that already assumes that the natural shape of a while loop is
while
cond
block
do
body
block
and that’s not my experience. I have somewhile loops like that, but the overwhelming majority are of the form
while condExpr do
body
block
Therefore, my expectation of the shape of a while loop is the latter. If it’s missing a do, it must be that condExpr do that went away, not the body block.
Contrast that with an if/then/else. There, the vast majority of them look like
if condExpr then
thenBlock
else
elseBlock
and therefore, if an else is missing, I indeed expect the else elseBlock to have gone away.
Likewise for try/finally. The expectation is that it is
try
tryBlock
finally
handler
It could be written
try tryExpr finally
handler
but that’s not the expectation. If it were the most common shape of try/finally block, then I would assume
try
something
to mean that the tryExpr finally had been elided. But it’s not the case.
Honestly I find both of them really hard to parse, there is nothing to distinguish the exit condition from the rest, and so at a glance I would probably read it as an infinite loop like @sjrd
This is why we had the do-while syntax in the first place
If we had to improve the syntax around these kinds of loops, I’d much rather have something that works in all cases and highlights the condition.
Something like the following (very much WIP):
looping:
preamble
while_true test // or `break_if !test` or `until !test`
postamble
Here is an example with existing keywords to see what it would look like with syntax highlighting:
(But we can’t use these, as that would be ambiguous)
while do:
preamble
break if !test
postamble
But I’m not sure we should encourage people to use while loops in the first place, and so I’m hesitant to add syntax to make them better
// Assuming we start with var i = 0
scala> loop:
| i += 1
| loop.stop(i > 3).?
| println(s"The value is $i")
|
The value is 1
The value is 2
The value is 3
(I’ve standardized on ? as the universal jump symbol–still trying to figure out whether an opaque jump object with .? or a method like stop_? is clearer. I always have warn on value discard on–otherwise the method is definitely superior.)
I think the argument from “expectation about shape of format” is not strong.
I’m recently less sanguine about one-liner
if condition then println() // side effect way on the right
so my opinions are fluid and open to influence from reviewers.
I sympathize with “highlight the condition”. But since we’re waxing nostaglic for goto, maybe the real evil all along was blocks.
Perhaps instead of “[Do] Gather ye rose-buds while ye may,” our grammatical model for while should be the verb, as in the Scarecrow’s lyric: “I could while away the hours, conferring with the flowers.” Then it’s much more natural for while to take a block. I don’t have an answer for making the result expression of the block more visually distinctive, though I have proposed in some context that end supply a value:
while
f()
end i > 3
I may have suggested for excessively long methods
def f() =
def preamble() = ???
begin
stuff()
end result() // in lieu of terminal return result()
def moreDefs() // but no eagerly evaluated exprs
Or was it end f with result. Then, end while with i > 3.
Since double semi was rejected for line comment, we still have available the true idiom for while true, namely
for ;;
f()
thereby eliminating that ambiguity for do-less while. Maybe more idiomatic for Scala:
However, semantically, {} means “this is a block but it does nothing”. (), in contrast, means “nothing to return, here is a placeholder instead”.
So even though {} actually implies (), and if you think it through, just inlining the () seems simpler, I prefer {} as a semantic indicator for what I’m thinking.
(Which is “this is an empty code block” not “here’s a placeholder value for you”.)
Thank-you, you have given me food for thought, namely, if blocks are evil, does that also mean that empty blocks are evil?
I won’t argue over our preference for brackets that are pointy or smooth, but I do wonder, not unseriously, if there is a missed opportunity for the equivalent of uninitialized (for underscore syntax) or ??? (just to mean not implemented).
To me this was true in the past. But now with Scala 3 doing away with do-while loops, all my do-while loops have become while-do () loops with large while blocks and degenerate do blocks!
In addition to the small number of existing while-do () loops from before (e.g. because things like do { val x = … } while (x > 0) were not allowed, but while {val x = …; x > 0} do () were), these while-do () loops now make up perhaps ~half of my while-do loops overall, and are no longer a negligible edge case as they were before