Scala allows local blocks as in
def foo(x: Int) = {
val y = x
{ val z = y * y
println(x)
}
y -1
}
This is dangerous, as it only works if there is an empty line in front of the local block. Dropping the empty line can give obscure type errors or even change the meaning of the program since then the block is treated as an argument to a function on the preceding line.
Scala already has locally in Predef. The example above is better written like this:
def foo(x: Int) = {
val y = x
locally {
val z = y * y
println(x)
}
y -1
}
I believe Scala 3 should mandate locally and not allow naked local blocks. Changing this would entail:
errorOrMigrationWarning in dotc).locally.locallyIt would be good to get this in by feature freeze at the end of this release cycle. If there's a volunteer I am happy to review.
yes please!
not only are local blocks dangerous to write, they're hard to read. the parser in our heads, too, normally expects braces to mean something else. it's better to give our mental parsers the explicit locally to avoid any guessing/backtracking
I suspect it will turn out that such blocks are pretty rare in actual code. at least, I can't remember the last time I encountered one. (in my own code, I have always used locally instead, but even that need rarely arises.)
assuming this goes forward in Dotty, then a deprecation PR in 2.13.x would also be accepted, I think.
question, though: how does locally look in the indentation-based syntax?
As a fan of local blocks to better demarcate lifetimes of local vals/vars I think I should weigh in.
May I humbly suggest that the name locally is a suboptimal name鈥攅specially for those of us who very frequently write distributed code?
Possible alternatives:
scopesectionEDIT: I realize that it is possible to do import Predef.{locally => scope}, but since we are looking to expose a rather new name in a bunch of existing sources, I'd rather get it right now rather than later.
It would be good to have locally be an inline def so that the compilation of the old and new styles are identical. Importantly, we want to avoid boxing vars.
scala> def actLocally: Unit = { var x = 0; locally { x += 1 } }
def actLocally: Unit
scala> :javap actLocally
...
public void actLocally();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=1
0: iconst_0
1: istore_1
2: getstatic #25 // Field scala/Predef$.MODULE$:Lscala/Predef$;
5: iload_1
6: iconst_1
7: iadd
8: istore_1
9: getstatic #31 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
12: invokevirtual #35 // Method scala/Predef$.locally:(Ljava/lang/Object;)Ljava/lang/Object;
15: pop
16: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
1 15 1 x I
0 17 0 this L$line18/$read$$iw$$iw$;
I am not sure dropping naked block will have any positive effect in the long run. In a world where naked blocks have completely disappeared, I will still be able to write
def foo(x: Int) = {
val y = x
{
val z = y * y
println(x)
}
y -1
}
and it will still do the wrong thing, without any warning. So what did I gain?
I agree that the blank line thing is annoying, but IMO the fault lies in allowing the block to start on the next line rather than at the end of the previous line. I'd like that to be deprecated, so that I actually receive a warning when I did the wrong thing, and no warning when I did the right thing. But that is probably not possible because it would break all the code that uses that style.
(Also, this change will break the consistency rule that every expression is also a valid statement.)
locallyis a suboptimal name
We cannot do this in 3.0 without pulling additional tricks, becsause 3.0 ships without its own standard library, it uses the binary one from 2.13.
without any warning. So what did I gain?
It's an error though :-) We could probably improve the compiler's message?
It's also an error today, when you're not unlucky. My point is, the proposed change will not improve the situation. It will only make today's correct programs invalid, without making today's buggy programs invalid. So I repeat my question: what have I gained?
@lrytz
We cannot do this in 3.0 without pulling additional tricks, becsause 3.0 ships without its own standard library, it uses the binary one from 2.13.
We get around this currently by having a DottyPredef besides Predef that contains some inline functions (and, yes, locally or whatever it ends up being called should be an inline function). But I think it might also be possible to make a 3.0 specific Predef that contains these inline functions but is binary compatible to the 2.13 Predef.
@sjrd
So I repeat my question: what have I gained?
We removed for future programs the ambiguity and the trap it results in. It is one thing to say: "Don't do that" and another to forbid it. If there are no local blocks, period, people will be less inclined to use them wrongly.
question, though: how does locally look in the indentation-based syntax?
The same as now. The currently implemented scheme requires braces. Under the non-standard option -Yindent-colons you could write
def foo(x: Int) =
val y = x
locally:
val z = y * y
println(x)
y - 1
But the status of that option is quite uncertain now. That part of the proposal was retracted since it got too much opposition.
@sjrd
But that is probably not possible because it would break all the code that uses that style.
Do we have any cases we know use this style? Or should we actually look at how this affects the community build?
@odersky
We removed for future programs the ambiguity and the trap it results in. It is one thing to say: "Don't do that" and another to forbid it. If there are no local blocks, period, people will be less inclined to use them wrongly.
I agree with removing ambiguities and traps. But what don't you like about Seb's idea of forbidding applications to start on a new line? I would've thought you would've liked not losing the "every expression is also a valid statement" consistency rule.
Question: do we need this now? I get the argument for many features for Scala 3, but the "need to rewrite the textbooks" argument doesn't seem to hold for this relatively rarely-used aspect of the language. It seems like it could do with more discussion, and a proper deprecation cycle; I don't see a lot of need to rush it into Scala 3.0.
It seems this is a hornets nest. I withdraw from the discussion for now. If someone wants to move this forward, you have my support.
I don't see a lot of need to rush it into Scala 3.0
Local blocks are definitely a trap, it would be good to do something about it.
How about
inline def scope to DottyPredef-language:Scala2, add a warning and rewrite rule on local blocks to use scopedoes not take parameters, missing argument or type mismatch) if the function is a value (not a method) and the argument is a block on a new line - we can maybe not make this perfect, but there might be an 80/20 solution.IMO, @sjrd's suggestion is too breaking, some people use the c-style syntax. If we support it for built-in control structures, it has to work for custom too:
scope
{
println()
}
While there may be some projects that use c-style wrapping (or "indentation") for definitions, I would want to see if there's actual usage of it for block arguments... Similarly for built-in control structures.
While there may be some projects that use c-style wrapping (or "indentation") for definitions, I would want to see if there's actual usage of it for block arguments... Similarly for built-in control structures.
In my experience there's always a lot more usage than I first thought, but it tends to come out of the woodwork very late. Typically, enterprise projects differ more in their coding conventions than open source. So, disabling a common (in other languages) convention like that would make me very nervous.
Example: Nobody thought that early initializers were used much, until I learned that Expedias entire architecture was built on them.
Will the following still work after the proposed change?
def foo(arg : Int) : Unit = ???
foo({println("something"); 0})
It has to, good example! It might be not that easy to identify local blocks. While your example would also work without the parentheses, foo(0, {println(); 0}) cannot be shortened.
Do we really need locally? The meaning of the following might be clearer:
def foo(x: Int) = {
val y = x
val () = {
val z = y * y
println(x)
}
y -1
}
@soronpo Yes. The definition of a local block would be a block that appears as one of the statements in a statement sequence.
Most helpful comment
I am not sure dropping naked block will have any positive effect in the long run. In a world where naked blocks have completely disappeared, I will still be able to write
and it will still do the wrong thing, without any warning. So what did I gain?
I agree that the blank line thing is annoying, but IMO the fault lies in allowing the block to start on the next line rather than at the end of the previous line. I'd like that to be deprecated, so that I actually receive a warning when I did the wrong thing, and no warning when I did the right thing. But that is probably not possible because it would break all the code that uses that style.
(Also, this change will break the consistency rule that every expression is also a valid statement.)