On Tue, Dec 23, 2014 at 4:00 PM, William ML Leslie < [email protected]> wrote:
> On 24 December 2014 at 06:30, Geoffrey Irving <[email protected]> wrote: > > In python, > > > > closures = [] > > for i in range(10): > > closures.append(lambda x:x+i) > > > > makes 10 copies of lambda x:x+9. > This is timely, because Eric Northup just stopped by and we were talking about this case. He points out that it's a very common pattern when kicking off concurrent sub-programs, and it messes people up. The question is: do closures capture variables by value or by reference? The classic answer is "by reference", and the example above illustrates why this can get you in trouble in a language having mutable variables that also performs capture by reference. Eric notes further that C++ lambdas allow you to say explicitly which one you want, and that this has gone a long way to reduce a certain class of errors by letting people say what they mean. In the case above, the issue need not have arisen in the first place, because the induction variable in a range iterator isn't getting updated. It's getting rebound. In the view of a purer language design, each of those captures is capturing a different value. So yes, Python gets this wrong, because /i/ is going out of scope at the close of each loop. From the BitC perspective, by-reference capture should be producing a static type error because the captured reference exceeds the lifespan of its initialization. That isn't true an updating iteration. Given for (i = 1; i < 10; i++) closures.append(lambda x:x+i) The /i/ is *not* going out of scope, so the distinction between by-value and by-reference capture becomes significant. Further, the region may not expire, so we may not get a static type error. It is *possible* to resolve this in BitC by writing something like: for (i = 1; i < 10; i++) let i = i in closures.append(lambda x:x+i) but I think this places the burden on the wrong side of the discussion. I think what I'm coming down to here is that capture of mutables should be done by (shallow copied) value. > The *other* possibility is to say that the variable introduced by the > loop is fresh, as in the following java: > > for ( final int i : someIterable ) { > closures.append(x -> x + i); > } > > If you don't require variable declarations, this seems a bit tricky... > Not tricky at all. The clear resolution is that range-based /for/ is *defined* to introduce a fresh variable. > because does the loop body also introduce a fresh scope? Java says > yes, but this is awkward in practice. In functional languages, the answer to this has always been "yes". It seems to me that the default should be by-value capture. The more interesting question is whether it's worth the bother to introduce an overriding construct of some form. I see two ways to do this: let captured mutable x = *initializer* in ... or some annotation at the lambda itself indicating the type of capture it means to be doing. Hard to say which is cleaner, though in *either* case I think the iterator pattern should be using a fresh variable. BitC is trying to favor non-imperative constructs where possible. One advantage to the "let captured" construct is that it tells you explicitly that the mutable value isn't shallow. shap
_______________________________________________ bitc-dev mailing list [email protected] http://www.coyotos.org/mailman/listinfo/bitc-dev
