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

Reply via email to