4 ms·
Aside from Python and the Shell, it never occurred to me that any language could possibly think of other semantics. It's news to me that C++ also assigns rather
by Blikkentrekker 4y ago
Aside from Python and the Shell, it never occurred to me that any language could possibly think of other semantics. It's news to me that C++ also assigns rather than initializes on each iteration.
It's simply a very bad idea that provides no use yet creates many bugs.
- masklinn 4y agoThe thing is it only create many bugs when you throw closures into the mix. So historically languages did it because it was easy to implement (create a counter, increment the counter, run the loop body). Before the early aughts, closures were mostly really common in functional languages which tend towards immutable bindings (and immutability in general), and very closure-focused languages closured everything so didn’t hit that issue (e.g. you wouldn’t hit it in Smalltalk because your counter would be a parameter to a block, so closing over that was no issue). It’s really in the 00s with the explosion of callbacks-pile-javascript (and more generally the functionalisation of imperative languages) that the problem became a serious concern: you loop over a thing, you start some sort of async operation (network request for instance), and you find out that despite the request being correct the entire thing goes wonky (then again things commonly went wonky which didn’t help).
- Blikkentrekker 4y ago> The thing is it only create many bugs when you throw closures into the mix. So historically languages did it because it was easy to implement (create a counter, increment the counter, run the loop body). It's a problem with references in general as this shows. I also don't feel it's easier to implement at all. One can either rewrite: for $id:var in $exp:iter { $code:body } to: { let $id:var; while(True) { let result = $exp:iter.next(); if(result.is_none()) break; $id:Var <- result.extract(); $code:body } } Or while(True) { let result = $exp:iter.next(); if(result.is_none()) break; let $id:var = result.extract(); $code:body } The latter implementation is as far as I see easier, not more complex. Obviously all the code to create scoping already exists in the compiler and for-loops over an iterator work with a syntactic rewrite to an infinite loop with a break.
- masklinn 4y ago> It's a problem with references in general as this shows. Most languages don't have references, and in those that do before the issue was understood, the explicitness made it a much smaller issue. > I also don't feel it's easier to implement at all. > One can either rewrite: Now try lowering to bytecode or assembly instead of high-level pseudocode.
- Blikkentrekker 4y ago> Most languages don't have references, and in those that do before the issue was understood, the explicitness made it a much smaller issue. But Go and C++ do, where this issue arose with or without closures. > Now try lowering to bytecode or assembly instead of high-level pseudocode. It doesn't matter, because as I said, all that is already in the compiler. It would be needlessly complex and error-prone for compilers to hardcode custom code generation for such abstractions; it's transformed to something else the compiler already understands at a far higher level. I know for a fact that in Rust, for-loops already desugar to a simple infinite loop construct with a break at the H.I.R. level and all further optimizations only happen from there.
- tialaramex 4y agoYup. C# likewise described the change in semantics as just a different de-sugaring of their for each loop when that happened.
- Blikkentrekker 4y agoWhich makes it more confusing why it was originally as it was because it's really not harder to implement as any compiler implements it as desugaring before optimizations een occur and this form is simpler. The only explanation I see is that they really gave it no thought at all whatsoever and it wasn't a tradeoff but simply not thinking clearly.