The function MapAll开发者_开发知识库
was seen as important enough to warrant the short form //@
, yet I rarely use it, especially compared to others like /@
, /.
and @@@
which I use almost everywhere.
What applications best leverage
MapAll
?Is it used mostly in certain fields or programming styles?
How often can it be used compared to other operators?
//@
is a "post-order tree traversal". It visits every node in a tree structure, each node's children being visited before the node itself. The supplied function is called with each node as its argument, the node's children already having been "expanded" by a previous call. Tree data structures are common, along with the need to traverse them. But I dare say that the primary use case for //@
in a Mathematica context is to implement evaluators.
Let's start by creating a random tree-structured expression:
In[1]:=
$expr = 500 //.
n_Integer /; RandomInteger[100] < n :>
RandomChoice[{p, m}] @@ RandomInteger[Floor[n/2], 2]
$expr//TreeForm
Out[2]= p[m[p[34, 22], m[11, 24]], p[m[6, 7], 10]]
Let's say that we want to create an evaluator for a mini-language using expressions of this form, where p
means "plus" and m
means minus. We can write a recursive-descent evaluator for this mini-language thus:
In[4]:=
eval1[p[a_, b_]] := eval1[a] + eval1[b]
eval1[m[a_, b_]] := eval1[a] - eval1[b]
eval1[a_] := a
In[7]:=
eval1[$expr]
Out[7]= 78
It gets tiresome to have to explicitly write the recursive calls to eval1
in each of the rules. Furthermore, it is easy to forget to add the recursive call in a rule. Now consider the following version of the same evaluator:
In[8]:=
eval2[p[a_, b_]] := a + b
eval2[m[a_, b_]] := a - b
eval2[a_] := a
The "noise" of the recursive calls has been removed so that the rules are easier to read. Surely we can find some way to automatically insert the necessary recursive calls? Enter //@
:
In[11]:=
eval2 //@ $expr
Out[11]= 78
It does just what we need. With slight abuse of terminology borrowed from functional programming, we can say that we have lifted eval2
to be a recursive-descent function. You can see the effect in the following diagram.
In[12]:=
"eval2" //@ $expr // TreeForm
Postscript
In Mathematica there are always many ways to achieve an effect. For this toy evaluator, all of the preceding discussion is overkill:
In[13]:=
$expr /. {p -> Plus, m -> Subtract}
Out[13]= 78
... if only it were always so easy to check if an evaluator was giving the right results :)
I used it a few times to make an inert representation of code which may execute and which you want to transform or destructure without any evaluation. Here is an example:
ClearAll[myHold, makeInertCode];
SetAttributes[{myHold, makeInertCode}, HoldAll];
makeInertCode[code_] :=
MapAll[myHold, Unevaluated[code], Heads -> True]
Here is an example:
In[27]:=
icd = makeInertCode[
With[{x = RandomInteger[{1, 10}, 20]},
Extract[x, Position[x, _?OddQ]]]
]
Out[27]= myHold[myHold[With][myHold[myHold[List][myHold[myHold[Set][myHold[x],
myHold[myHold[RandomInteger][myHold[myHold[List][myHold[1],myHold[10]]],myHold[20]]]]]]],
myHold[myHold[Extract][myHold[x], myHold[myHold[Position][myHold[x], myHold[myHold[
PatternTest][myHold[myHold[Blank][]], myHold[OddQ]]]]]]]]]
Now we can use the standard destructuring tools without the danger of premature code evaluation (to be totally sure, myHold
can be given HoldAllComplete
rather than HoldAll
attribute):
In[28]:= Cases[icd, myHold[Extract][___], Infinity]
Out[28]= {myHold[Extract][myHold[x],
myHold[myHold[Position][myHold[x],
myHold[myHold[PatternTest][myHold[myHold[Blank][]],
myHold[OddQ]]]]]]}
once the code is transformed / destructured, it can be wrapped in Hold
or HoldComplete
, and then the myHold
wrappers can be removed by, for example, a rule like myHold[x___]:>x
, applied repeatedly. But generally, the added value of MapAll seems to me rather limited, because, in particular, Map
with the level specification {0,Infinity}
is equivalent to it. I don't think it is frequently used.
I don't use it, but has some fun behavior for Listable functions. For example:
If you want each element in a list to have a function applied to it a number of times depending on its nested depth in the list, I guess this the only time I've seen it used.
SetAttributes[f, Listable]
(f //@ {{a}, {{b}}, c}) // Flatten
{f[f[f[a]]], f[f[f[f[b]]]], f[f[c]]}
Generally though I would imagine you could use ReplaceAll whenever you would use this.
I would use it as a lazy way to apply algebraic expressions on objects algebraic functions do not work with:
In[13]:= ser = 1/(1 + x)^a + O[x]^4
Out[13]= SeriesData[x, 0, {
1, -a, Rational[1, 2] (a + a^2),
Rational[1, 6] ((-2) a - 3 a^2 - a^3)}, 0, 4, 1]
In[14]:= Factor[ser]
Out[14]= SeriesData[x, 0, {
1, -a, Rational[1, 2] (a + a^2),
Rational[1, 6] ((-2) a - 3 a^2 - a^3)}, 0, 4, 1]
In[15]:= MapAll[Factor, ser]
Out[15]= SeriesData[x, 0, {
1, -a, Rational[1, 2] a (1 + a),
Rational[-1, 6] a (1 + a) (2 + a)}, 0, 4, 1]
精彩评论