You can even just use `do { … } while (false)` without comments, but neither is an expression. Neither produce a value. The do expression would have similar encapsulation but allow assigning a derived value to a lhs binding, as you could do in a lisp (or Rust [or Swift, or…]).
If your code is sufficiently distinct that block scope is useful, and there's no obvious contextual clue as to why it's distinct (like an if statement), put it in a separate function to explain why it's distinct. The article mentions this argument, but responds with some handwaving about how technically it's valid JS to use an unnamed block statement. I mean, sure, but does that mean it's a good idea?
I've used this method in C code. One reason---C89 only allows variable declarations at the top of a block, and if I have a small section of code towards the middle of end of the function with its own variable use, the declaration is separated from the use. By using a new scope, the declaration can be done right where it's used. Yes, it looks a bit weird, but there could be benefits.
I also use this in Lua. The benefit there---once the scope is over, the variables declared in the scope become available for garbage collection. In a small function, this isn't that much of a win, but for the top level scope, it probably is. You can see an example of this in my gopher server [1]. Don't let the formatting confuse you, this:
local CONF = {} do
local conf,err = loadfile(arg[1],"t",CONF)
if not conf then
io.stderr:write(string.format("%s: %s\n",arg[1],err))
os.exit(exit.CONFIG,true)
end
-- rest of code
end
is the same as
local CONF = {}
do
local conf,err = loadfile(arg[1],"t",CONF)
if not conf then
io.stderr:write(string.format("%s: %s\n",arg[1],err))
os.exit(exit.CONFIG,true)
end
-- rest of code
end
The 'do' keyword introduces a new scope (and can be used anywhere to do so). I format it as the former to be more explicit about the CONF variable being defined by the following code block.
Oh, one other reason to do the new scope instead of a function---you can still use variables in the outer scope that you might otherwise have to pass in to a function.
Yes, to me it does. I love when variables have really tight scopes so I don't need to worry about if/how they are used throughout the rest of the program. Locality should be the default, not something that needs to be justified.
Creating a function is just unnecessary boilerplate if the code is not going to be reused. Functions are just named blocks with arguments. But if you don't need to name the block and don't need to reuse it with varying arguments, creating a function is just unnecessary.
Functional programmers are familiar with a concept called "let", which essentially gives you a way to introduce a variable and introduce a new scope just for that variable. This article is essentially just showing how to do that in JavaScript.
I have a long but pretty linear function whose job it is to take a whole mess of data and build a SAT model. Even with even with jump-to-definition etc lots of little[1] sub-functions make the coffee difficult to read. I would kill for block scope (yay python) to ensure that I'm not accidentally reusing a temporary variable from three steps ago.
Hmm. Now you've got me thinking about the most ergonomic way do define a block in python. Maybe a no-op context manager? So you could do "with scope():"
Ignoring any future/proposed syntax, I think the most block-like would be a local function definition which you then call once right afterward. This is the same you would do in any LISP-like language.
It could take zero arguments if you just want to temporarily extend the current scope with a few more variable-binding statements. Or, it could also have arguments if you want to do a "let" like construct to rename some expression results in the outer (calling) scope during the invocation.
# ... outer scope
x = expr1
y = expr2
r1 = None
r2 = None
def block1():
nonlocal r1
z = expr3
r1 = expr4
block1()
def block2(x, y):
nonlocal r2
z = expr5
r2 = expr6
block2(expr7, expr8)
A separate function requires a mental "push/pop context" when reading the code to find and read the called function and then returning to the caller function, a simple scope block doesn't require such mental stack gynmastics (yes, some IDEs can help by visually inlining the called function back into the caller function, but that's just undoing an action that probably shouldn't have happened in the first place).
Sounds unreasonable at first, and maybe you're being sarcastic, but I've actually found that trying that as a discipline helps with readability, because each const variable gets a very descriptive name related to the action.
18 comments
[ 169 ms ] story [ 779 ms ] threadThere's also a GCC extension for this! https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
I'm using this for something a bit like Rust's unwrap (but uglier):
I also use this in Lua. The benefit there---once the scope is over, the variables declared in the scope become available for garbage collection. In a small function, this isn't that much of a win, but for the top level scope, it probably is. You can see an example of this in my gopher server [1]. Don't let the formatting confuse you, this:
is the same as The 'do' keyword introduces a new scope (and can be used anywhere to do so). I format it as the former to be more explicit about the CONF variable being defined by the following code block.[1] https://github.com/spc476/port70/blob/master/port70.lua#L41
Yes, to me it does. I love when variables have really tight scopes so I don't need to worry about if/how they are used throughout the rest of the program. Locality should be the default, not something that needs to be justified.
Creating a function is just unnecessary boilerplate if the code is not going to be reused. Functions are just named blocks with arguments. But if you don't need to name the block and don't need to reuse it with varying arguments, creating a function is just unnecessary.
Functional programmers are familiar with a concept called "let", which essentially gives you a way to introduce a variable and introduce a new scope just for that variable. This article is essentially just showing how to do that in JavaScript.
[1] honestly, not so little
I admit to using block scopes in rust a fair bit
It could take zero arguments if you just want to temporarily extend the current scope with a few more variable-binding statements. Or, it could also have arguments if you want to do a "let" like construct to rename some expression results in the outer (calling) scope during the invocation.
Edited for typos, code formatting, and bugs ;-).Fun read.