Hacker News new | ask | show | jobs
by parenthephobia 3568 days ago
In Ruby, you could do:

    10.times.map do |i|
      -> { puts i }
    end.shuffle.each(&:call)
This makes 10 lambdas which print the current value of the loop and then calls them in random order. Each puts sees a different i.

I don't think the behaviour in the article is really a problem with loop variables, but with defer. It is odd that defer packages up the function and its arguments when it is defined. Deferring printing i remembers the value of i at that time, whilst deferring printing &i remembers the (unchanging) address of i.

OTOH, having defer remember the values makes things like

    f = open("file1")
    defer f.close()
    ... do stuff ...
    f = open("file2")
    defer f.close()
close both files, which is probably what the programmer expected. I think that's pretty horrible code, but either way some people are going to find the results surprising.

Go's "problem" is that it can't efficiently give i a new address in each iteration: it'd have to put each one in a new memory location.

2 comments

Go's problem here stems from the fact that it relies on C-style for loops for this kind of thing instead of iterators. With iterators you can define the language semantics to provide a fresh location on every iteration. It's yet another point against C-style for loops...
> Go's "problem" is that it can't efficiently give i a new address in each iteration: it'd have to put each one in a new memory location.

It's not an efficiency issue. How do you think Ruby does it?

Then for loops would be linear in space.
The block inside the loop must be kept in memory whilst there are still anonymous functions which reference the per-iteration loop variables. Loops are linear in space if such functions are being generated and retained in each iteration. If they don't, then iteration stack frames are cleaned up by GC.

This applies to each-style loops. Actual Ruby for loops don't have this behaviour.

    a = []
    for i in 1..100
      a << ->{ puts i } # append, to a, a function that prints 'i'
    end
    a[0].call()
This prints 100, since the scope of i covers the entire loop, and not each iteration. They are, however, much faster than each-style loops, since they don't have to make a stack frame for each iteration.
Garbage collection is formally defined as an infinite memory simulator[1], so this is not a problem for collected languages.

[1]: https://blogs.msdn.microsoft.com/oldnewthing/20100809-00/?p=...