Local Variables
craftinginterpreters.com
craftinginterpreters.com
https://twitter.com/munificentbob/status/1109876226104057856
{
var a = "outer";
{
var a = a;
}
}
and {
var a = a;
}
. Some programming languages have the idea of "let rec" versus "nonrecursive let." In Haskell, `let` is recursive, and because Haskell is lazy, `let a = a` is a nonterminating computation. In OCaml, `let` is non-recursive and `let rec` is recursive, and only certain recursive definitions are allowed. (You can do `let rec a = 0::a`, but not `let rec a = a`.)Scheme has `let`, where none of the variables are in scope for any definition, `let*`, where each variable is in scope in the successive definitions, and `letrec`, where all of the variable are in scope for all definitions. According to http://www.r6rs.org/final/r6rs.pdf, Scheme initializes letrec'd variables to a "black hole."
I am pretty sure that lazyness has nothing to do with this.
Doing my research, I was wrong about the "nonterminating computation" part; when the thunk gets forced, it gets marked as a "black hole," detecting the infinite recursion in this case.
With function scope I can declare variables where they are used, they will be automatically hoisted. This also solves the issue with using a variable before they are declared ... you declare variables where they are used.
Function scope also encourages the use of functions and closures, which makes (async) programing more fun.
In the book, Bob says he designed the language such that it would be possible to use just one stack. What does that mean? It sounds very discouraging for newbies because it implies a lot of foresight or trial and error.
Overall, while this is among the most interesting subjects covered, I find the chapter a bit lacking in either one of two ways: either it is for beginners, and then the chapter feels very magical or is for intermediates and then a bit of extra verbiage on the design choices that led you there would be appropriate.
Nitpick: I would totally have stopped low and done the -1 initialization trick as well. But yuck. ;)
Unfortunately, since some dynamic languages do not respect lexical scoping, programmers in these languages tends to think of local variables and scoping as something very complicated. It doesn't need to be.
The author describes this additional constraint that they have on top of lexical scoping.
> We have to be OK with only allocating new locals on the top of the stack, and we have to accept that we can only discard a local when nothing is above it on the stack.
(Don’t get me wrong, this obviously still implies additional constraints, but it gets fairly close to universal closures.)
I don't know if lambdas work the same way. I know in some ways they work like anonymous types, and not in others.
Here is the code that generate the constructor of a lambda proxy http://hg.openjdk.java.net/jdk/jdk/file/3cabb47758c9/src/jav...
Look up the upwards funarg problem.
> The upwards funarg problem arises when the calling function refers to the called/exited function's state after that function has returned. Therefore, the stack frame containing the called function's state variables must not be deallocated when the function returns, violating the stack-based function call paradigm.
C++ violates the constraint we're talking about.
> Look up the upwards funarg problem.
I’m well aware of upward funargs and what I’ve described specifically implements upward funargs in C++. See https://godbolt.org/z/KZiBNB. Note that this code does not perform any heap allocation, and the closed-over variable `i` is saved from the local scope and made available to the caller via the lambda.
That was the context I thought we were having the conversation in - capturing local variables. I'd describe what you mean as capturing local values. If you're copying the value from a variable then you're not capturing the variable.
I can see why you're arguing it your way now.
C++ doesn't do such thing automatically, but there's nothing preventing you to do it yourself. If you intend to use a local variable whose lifetime would not normally outlast the closure, feel free to use std::move and a move constructor. In general C++ gives you the tools to manage memory manually or semi-automatically, but not totally automatically. And it also doesn't, unlike Rust, force you to use memory correctly.
Isn't that the only way to learn from any programming book ? At least that's what I have always done. I am curious as to what other way people learn.
After that, you could maybe port it to a different language. But doing that the first time around would be a little hard. Especially if you wanted to keep the code idiomatic to the language.