47

I noticed this fact, that may be misleading for programmers used to C language.

In Mathematica, if you have a function f[] and an array v, and you write

v[[ f[] ]]++

the function f is called twice. Probably experts knows this very well, but I used MMA for years ignoring this. Normally this behaviour is harmless, but this should be taken into account if f is costly, has side-effects or can return different values based on the same input.

Indeed, I realized this property of ++ because of this:

t={0,0,0}; r[]:=RandomInteger[{1,3}]; Do[t[[r[]]]++,{10000}]; Print[t," ",Total[t]]

where I increment a random entry of t 10000 times, but at the end the sum of entries of t is not 10000.

This is insidious for C programmers, because in C if you write a similar code v[f()]++, the function f() is called just once.

I would like to ask if this semantic of ++ is somehow "forced" by the overall structure of Mathematica language or if they could have implemented it differently.


Keywords: Increment Preincrement HoldAll Hold Evaluate twice

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
Giovanni Resta
  • 733
  • 5
  • 6
  • 15
    The interesting thing is that me and most other people I know who use Mathematica don't run into problems like these. The reason is that we program functionally, which is a more natural way to use Mathematica. It's true for any language that if you try to program it like another language, you're going to have a bad time. – Searke Feb 17 '16 at 20:55
  • 7
    I split my time between Mathematica (fast to code) and C (fast). I generally tend to use functional programming in Mma (I never in my life used a For[]), but ++ is so basic I did'nt notice the HoldFirst attribute. – Giovanni Resta Feb 17 '16 at 21:01

1 Answers1

32

I believe Increment (more accurately PreIncrement as george2079 noted) is essentially this:

SetAttributes[inc, HoldFirst]
inc[a_] := a = (a + 1)

This exhibits the same behavior, e.g.

f[] := (Print[#]; #) &@RandomInteger[{1, 3}]

v = {0, 0, 0};
inc @ v[[f[]]];
2

1

Here f[] is evaluated twice because parameter a is used twice within Set. On the right hand side it draws the actual element to which to add one, and on the left hand side it is evaluated to get a valid index for assignment. (HoldFirst prevents the expression from being evaluated before it is substituted into the definition; a requirement for such an assignment to work correctly.)

This does follow from Mathematica design as a natural way to implement something like this, and evaluation follows established, if at times confusing, rules. This becomes apparent if one tries to avoid this problem. How exactly does one prevent double evaluation in a generic way? belisarius recommended in a comment memorizing f but that is a specific solution, not a broad one.

For the specific case of a Part one could add a rule:

inc[a_[[p__]]] := With[{eval = p}, a[[eval]] = (a[[eval]] + 1)]

Test:

v = {0, 0, 0};
inc @ v[[f[]]];
v
3

{0, 0, 1}

However this introduces a special case, and rather than clarifying behavior it may serve to confuse instead. After observing the above behavior one might expect this also to evaluate f[] only once, but it does not:

w[_] = 0;
inc @ w[f[]];
2

3

So we may end up chasing our tails trying to pin down special cases, rather than accepting the simple rule inc[a_] := a = (a + 1) and the evaluation it implies.

Other operators affected

This same mechanism affects not only Increment, Decrement, and their Pre- forms, but all special assignment operators:

v[[ f[] ]] += 2;

1

2

v[[ f[] ]] -= 2;

3

2

v[[ f[] ]] /= 2;

1

3

v[[ f[] ]] *= 2;

2

2

In the case of AppendTo and PrependTo this can lead to seemingly errant behavior as noted in Problem with function inside brackets. Bug?:

v = {{1}, {2}, {3}};

AppendTo[v[[ f[] ]], 4];

v

3

2

{{1}, {3, 4}, {3}}
PrependTo[v[[ f[] ]], 5];

v

1

2

{{1}, {5, 1}, {3}}

Rethinking my assertions

This answer has proven unexpectedly popular, and in such cases I try to "channel" Leonid and take it to the next level in an effort to deserve the attention.

While I believe what I wrote above holds if we are using the standard evaluation elements to emulate this functionality it is also true that there is nonstandard evaluation in various parts of the familiar language, one of these cases being Set itself. Consider that the statement v[[ f[] ]] = 1 manages to correctly change a part of the vector assigned to v, rather than generating an error as would happen if the entire LHS were fully evaluated, and yet it does evaluate f[] rather than attempting to assign something to part "f[]" verbatim. This partial LHS evaluation has been discussed before:

It occurs to me that Increment and PreIncrement could potentially use a similar special evaluation in an attempt to prevent exactly the kind of double evaluation under discussion. Unfortunately I cannot think of any simple way to emulate this special LHS evaluation in order to implement this myself. I shall continue to think on the problem, but perhaps WReach or Leonid will have an implementation to offer in the mean time.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • 2
    Technically you have cloned PreIncrement. (Same core issue though) – george2079 Feb 17 '16 at 21:08
  • @george2079 Thanks for pointing that out! – Mr.Wizard Feb 17 '16 at 21:09
  • 1
    Thanks for the answer. Once one knows about this, preventing the problem is easy. I think it would be nice a little note of warning in the "Possible issues" of the documentation of Increment, PreIncrement, and also AddTo. – Giovanni Resta Feb 17 '16 at 21:21
  • @Giovanni You're welcome, and I agree regarding the documentation. – Mr.Wizard Feb 17 '16 at 21:23
  • 1
    @GiovanniResta There's a feedback button at the bottom of each online documentation page. You can use that to suggest it. – Szabolcs Feb 17 '16 at 21:39
  • I have been thinking of this question for some time and I have some thing to tell here. The problem (as you can see) is mainly because of the randomly generated index. Do you think if we add an Upvalue to the Increment would that help. I don't know if this is a good answer or not but look at what I have Increment[ k_[[x_]] /; Or @@ (StringMatchQ[ ToString /@ Level[FullForm[ Trace[x] ], {-1}, Heads -> True], "*Random*"])] := k[[#]]++ &[x] – Basheer Algohi Feb 21 '16 at 20:59
  • Now to see this happen, I checked it with this example : r := RandomInteger[{1, 17}]; Clear[a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q]; list = {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q}; list[[r]]++ You can see that the increment happed exactly at the same position of the index. – Basheer Algohi Feb 21 '16 at 21:00
  • @Algohi Other processes could produce different values when evaluated more than once. Ultimately I don't think this is a good path to go down hence my comment "... chasing our tails ..." etc. While your code deals with this specific problem it actually causes more evaluation; try e.g. r := (Print["foo!"]; RandomInteger[{1, 17}]); and then observe that "foo!" prints three times. – Mr.Wizard Feb 22 '16 at 04:30
  • @Mr.Wizard, I know that it will print "foo!" more than one time but that is out side the process of incrementing the list[[index]]. If you check this with the OP you will see it gives the correct results. t = {0, 0, 0}; r[] := RandomInteger[{1, 3}]; Do[t[[r[]]]++, {10000}]; Print[t, " ", Total[t]] resulted in (*{3403,3300,3297} 10000*). The point here is that we will have more that one evaluation and that is inevitable but we can check if we are getting different results from each evaluation of indices and if so we use only one of them as an input for indices. – Basheer Algohi Feb 22 '16 at 15:59
  • 1
    @Algohi I do understand your point. (At least I think so.) However I personally find that behavior at least as confusing as the behavior than originated this question. If one is using side-effect code this will be even more bug-like than the original behavior. If you do prefer modifying the behavior of Increment why not avoid the multiple evaluation entirely? Unprotect[Part]; Increment[ a_[[p__]] ] ^:= With[{eval = p}, (a[[eval]] = a[[eval]] + 1) - 1 ] – Mr.Wizard Feb 22 '16 at 17:15
  • @Mr.Wizard yes that would be way better than what I have suggested. Thanks – Basheer Algohi Feb 22 '16 at 17:20