2

We didn't expect a replacement of B.

In[1]:= A = {B};
f[x_, y_] := AppendTo[A, {x, y}];
Do[Print[f @@ B], {B, {{X, Y}}}];
A

During evaluation of In[1]:= {{X,Y},{X,Y}}

Out[4]= {{X, Y}, {X, Y}}
Martin Ender
  • 8,774
  • 1
  • 34
  • 60
  • I thought B in the loop Do should be treated as a dummy variable. – wacharin wichiramala Apr 01 '16 at 07:54
  • 1
    Also interesting: Table[{A, B}, {B, {{X, Y}}}] gives {{{{X, Y}}, {X, Y}}}, but at least in this case, afterwards, A is still {B}. I think fundamentally, this is due to Do and Table using Block for variable localisation, which uses dynamic scoping. There's a great answer of Leonid's on what that means, but it's quite interesting that the B inside A actually gets evaluated in the course of the AppendTo. That's certainly somewhat unexpected. – Martin Ender Apr 01 '16 at 07:58
  • 1
    This doesn't really have anything to do with AppendTo, you can see the same behavior in A = {B}; Do[Print@A, {B, 2}]; – Jason B. Apr 01 '16 at 08:02
  • 1
    @MartinBüttner Yep, Do / Details-> Do effectively uses Block to localize values or variables.. – Kuba Apr 01 '16 at 08:02
  • 1
    @JasonB In that case, you still have A == {B} after the Do though, whereas in the OPs case, there is no B left in A after the loop. – Martin Ender Apr 01 '16 at 08:03
  • That seems expected, since AppendTo redefines its first argument, exactly as if you said Do[A = Append[A, B], {B, {1}}]; – Jason B. Apr 01 '16 at 08:06

2 Answers2

3

A workaround is to use lexical (Module) scoping to localize B for Do in addition to the dynamic scoping that Do already has.

Module[{B}, Do[f @@ B, {B, {{X, Y}}}]];

In short, Do does scoping by temporarily changing the value of B. This B is however still the very same symbol both inside and outside of Do, it just has a different value inside of Do.

Module will effectively cause B to become a distinct symbol, different from the one stored in the list A. It achieves this by renaming it.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
2

First, I recommend reading Leonid's amazing answer about scoping constructs. It explains very clearly what the difference between lexical and dynamic scoping is.

The docs say the following about Do:

Do effectively uses Block to localize values or variables.

That means Do uses dynamic scoping. That is, during the execution of the Do iteration, B has the value {X, Y} even if it doesn't appear explicitly in the loop. This doesn't quite explain yet why the value of B inside A is still changed after the loop though.

Compare your code:

Do[Print[f @@ B], {B, {{X, Y}}}];
(* {{X, Y}, {X, Y}} *)
A
(* {{X, Y}, {X, Y}} *)

To the following:

Do[Print[A], {B, {{X, Y}}}];
(* {{X, Y}} *)
A
(* {B} *)

The latter isn't too unexpected. When evaluating A for the Print, B has its local value so A evaluates to {{X, Y}} as expected. And afterwards B is undefined again (and A was never changed), so A reverts to {B}.

However, in your code, the B inside A is irretrievably lost. This is because AppendTo doesn't actually modify its first parameter in-place (leaving existing elements untouched), but actually returns a new list and replaces its first parameter with that. This is because lists are immutable. But to do so, AppendTo needs to evaluate A. You can think of AppendTo[A, x] as being defined like this:

A = Append[A, x]

That means, the A on the right-hand side gets evaluated. At this point, B has its value of {X, Y} and hence is part of the result of Append and is now stored in A.


I don't know if there's a viable workaround using Do which is safe regardless of which variables you've used before. (There is, see Szabolcs's nice answer.) Of course, Do is generally frowned upon though, and a better option would be to use a functional style using something like Scan which avoids the need for a named variable altogether:

Scan[AppendTo[A, #] &, {{X, Y}}]

Or better yet, also avoid the side effect:

A = Fold[Append[#, #2] &, A, {{X, Y}}]

(Of course, for this simple case, a simple A = Join[A, {{X, Y}}] would do, but I'm assuming your actual code has a somewhat more elaborate update function.)

Martin Ender
  • 8,774
  • 1
  • 34
  • 60