Search code examples
loopslanguage-featureswolfram-mathematica

ForEach loop in Mathematica


I'd like something like this:

each[i_, {1,2,3},
  Print[i]
]

Or, more generally, to destructure arbitrary stuff in the list you're looping over, like:

each[{i_, j_}, {{1,10}, {2,20}, {3,30}},
  Print[i*j]
]

Usually you want to use Map or other purely functional constructs and eschew a non-functional programming style where you use side effects. But here's an example where I think a for-each construct is supremely useful:

Say I have a list of options (rules) that pair symbols with expressions, like

attrVals = {a -> 7, b -> 8, c -> 9}

Now I want to make a hash table where I do the obvious mapping of those symbols to those numbers. I don't think there's a cleaner way to do that than

each[a_ -> v_, attrVals, h[a] = v]

Additional test cases

In this example, we transform a list of variables:

a = 1;
b = 2;
c = 3;
each[i_, {a,b,c}, i = f[i]]

After the above, {a,b,c} should evaluate to {f[1],f[2],f[3]}. Note that that means the second argument to each should be held unevaluated if it's a list.

If the unevaluated form is not a list, it should evaluate the second argument. For example:

each[i_, Rest[{a,b,c}], Print[i]]

That should print the values of b and c.

Addendum: To do for-each properly, it should support Break[] and Continue[]. I'm not sure how to implement that. Perhaps it will need to somehow be implemented in terms of For, While, or Do since those are the only loop constructs that support Break[] and Continue[].

And another problem with the answers so far: they eat Return[]s. That is, if you are using a ForEach loop in a function and want to return from the function from within the loop, you can't. Issuing Return inside the ForEach loop seems to work like Continue[]. This just (wait for it) threw me for a loop.


Solution

  • Thanks to Pillsy and Leonid Shifrin, here's what I'm now using:

    SetAttributes[each, HoldAll];               (* each[pattern, list, body]      *)
    each[pat_, lst_List, bod_] :=               (*  converts pattern to body for  *)
      (Cases[Unevaluated@lst, pat:>bod]; Null); (*   each element of list.        *)
    each[p_, l_, b_] := (Cases[l, p:>b]; Null); (* (Break/Continue not supported) *)