4 ms·
Err, no? Most languages will make a copy for scalar values (e.g. ints), e.g. Javascript and python do that. C++ and Rust go further and provide the user the abi
by roblabla 5y ago
Err, no? Most languages will make a copy for scalar values (e.g. ints), e.g. Javascript and python do that. C++ and Rust go further and provide the user the ability to take the arguments by value or reference, even for classes/structs, which gives you control over what you want. And in this specific case, `v` should be a new instance for each iteration really, it makes very little sense to say that a reference to v of a previous iteration of the loop stays alive for the next iteration. Which is why the program as it exists would be a compile time error in rust, for instance (Maybe in C++ as well, I'm not well-versed enough in the lambda rules of C++).
- jhgb 5y agoWhat does this have to do with values or references? They are orthogonal to scopes, and this example is an issue of scoping. A closure finds free variables in one of its enclosing scopes. If the for loop creates one mutable binding for its control variable, even multiple closures created within the loop's body can all see this single mutable binding because they all share the single environment with this mutable binding in which all of them were created, so they can all observe this binding mutating over time. There are no "arguments by value or reference" because the closure in the example is nullary -- it has no arguments at all.
- ImprobableTruth 5y agoThey're talking about capture by value or reference. If you use capture by value, created closures will use the value of captured variables at the time the closure is generated. e.g. in C++, where one has to make capture by reference explicit int i = 0; auto f = [x = i, &y = i] { //x captures i by value, y by reference std::cout << "Value:" << x << ", Reference:" << y; }; i++; f(); will print out "Value: 0, Reference: 1".
- jhgb 5y agoI'm not sure you can call this "a closure". That term has a rather specific meaning related to lexical scoping which simply doesn't deal with copies or references or anything of the kind. There's no "capture by value or reference" since it's not a copy of a value, or even a reference to it -- it's one and the same variable binding inside and outside of the closure (whereas a reference would be another variable name referring to the same value, or at least a differently scoped variable of the same name referring to the same value, but that's not lexical scoping anymore). To put it bluntly, C++ is faking closures, since without subjecting environments to GC or refcounting, it can't delete the shared environments at just the right time. They just jumped on the bandwagon of closures being fashionable nowadays and tried to emulate closures as closely as possible by constructing artificial automatically generated objects in the background using some syntactic sugar without breaking the existing language.
- ImprobableTruth 5y agoI don't really understand your objection. How is >at least a differently scoped variable of the same name referring to the same value, but that's not lexical scoping anymore not lexical scoping? That's literally just how closures work under the hood. And that's exactly why closures have to deal with references (or copies) - every implementation of them is a function pointer and a record for the lexical environment, the latter of which requires that at closure creation time the relevant bindings are captured, either by creating a reference or making a copy. I also don't understand why C++'s (or Rust's) closures would be 'fake'. If you capture by reference, they work exactly like the closures of other languages. Not having a GC just means you have to be wary of lifetimes. If you use a reference counting pointer (/some other GC'd pointer) you literally have the same closures as other languages.
- jhgb 5y ago> How is ... not lexical scoping? That's literally just how closures work under the hood. In the presence of references in the language, these two cases may be observationally indistinguishable (maybe, I'm not a C++ guru?) but they're still different language features. Lexical scoping does not require taking a reference. > And that's exactly why closures have to deal with references (or copies) No, they don't. They deal with bindings. References are either an explicit thing you create (as in, int &b=a; or such), or a parameter passing mechanism...but a free variable is neither (it's not a parameter to the closure in any case), and it exists even in languages with no notion of references whatsoever, such as for example Scheme. > I also don't understand why C++'s (or Rust's) closures would be 'fake'. If you capture by reference, they work exactly like the closures of other languages. Not having a GC just means you have to be wary of lifetimes. Yeah, and that's at least one of the things that make it fake. Why would you have to be wary about lifetimes? Closures in languages that support them Just Work(TM).
- CornCobs 5y agoWhat do you mean by "languages with no notion of references whatever"? At least in Scheme I think references are a pretty immediately obvious feature. It's not like every assignment does a deep copy, nor does free variable capture in closures. The moment you do stuff with set-car! and friends you are very explicitly working with references
- kortex 5y agoString is a pointer/slice type under the hood, even if you don't see the asterisk. So when you are passing v, you are passing a reference to a slice which gets updated by the loop. Since the goroutines can run in parallel, you print whatever v is pointing to at the time. C++ lambdas do the same thing if you pass a pointer. Python would do the same thing if you had a mutable non-threadsafe objects passed to threads. Eg if you iterate over a list of dicts, and modify the dicts, you'll get the same effect. So then the question becomes, why does v get re-used, instead of assigning a new pointer each time? Dunno, probably performance. I could see the latter generating a lot of garbage, which can be totally avoided if you don't pass mutable references to goroutines. What go really lacks IMHO is some kind of const modifier for these kind of references. Maybe it does, all I remember are const scalars.
- jhgb 5y ago> String is a pointer/slice type under the hood, even if you don't see the asterisk. So when you are passing v, you are passing a reference to a slice which gets updated by the loop. Since the goroutines can run in parallel, you print whatever v is pointing to at the time. You'll have the same problem with a simple integer (for example, as in https://go.dev/play/p/uqvRoO3ynZt https://go.dev/play/p/uqvRoO3ynZt). Nothing to do with v being a reference to something.
- earthboundkid 5y agoYou are wrong about Python. It absolutely does the wrong thing: >>> funcs = [] >>> for i in range(10): ... def f(): ... print(i) ... funcs.append(f) ... >>> [f() for f in funcs] 9 9 9 9 9 9 9 9 9 9 [None, None, None, None, None, None, None, None, None, None]