These three functions are similar (speaking commonly), and in some applications any of them could be used, yet they have very different special applications.
Rudimentarily:
Map
wraps (sub)expressions in a given Head, and returns the modified input
Apply
replaces Heads in (sub)expressions, and returns the modified input
Scan
"visits" (sub)expressions, evaluates each of them, and returns Null
(For each example below f
should be undefined; start with ClearAll[f]
.)
Map
Most basically Map
works like this:
f /@ head[1, 2, 3] (* shorthand for Map[f, head[1, 2, 3], {1}] *)
head[f[1], f[2], f[3]]
As with each of the functions under discussion it can operate at different levels:
Map[f, {{1, 2}, {3, 4}}, {0}]
Map[f, {{1, 2}, {3, 4}}, {1}]
Map[f, {{1, 2}, {3, 4}}, {2}]
Map[f, {{1, 2}, {3, 4}}, {0, 2}]
f[{{1, 2}, {3, 4}}]
{f[{1, 2}], f[{3, 4}]}
{{f[1], f[2]}, {f[3], f[4]}}
f[{f[{f[1], f[2]}], f[{f[3], f[4]}]}]
Map
is an additive process; it inserts additional heads into an expression at the specified levelspec and then evaluates it.
If the expression is held nothing evaluates. (Held meaning the outermost Head has an Attribute such as HoldAll
. Hold
has this attribute. Do not confuse the Head with the Attribute.)
Print /@ Hold[a[1, 2], b[3, 4]]
Hold[Print[a[1, 2]], Print[b[3, 4]]]
Apply
Apply
works like Map
, except that instead of adding a Head it replaces an existing one, if a head exists at the specified level. Unlike Map
it is frequently used at levelspec {0}
with the short form @@
, which replaces the outermost head:
f @@ head[1, 2, 3] (* short form of Apply[f, head[1, 2, 3], {0}] *)
f[1, 2, 3]
A second short form (@@@
) exists for levelspec {1}
:
f @@@ {{1, 2}, {3, 4}}
{f[1, 2], f[3, 4]}
If a sub-expression is atomic (has no operable Head) Apply
does not modify it:
f @@@ head[1, 2, x + y]
head[1, 2, f[x, y]]
Note that 1
and 2
are unmodified, while x + y
(which has a FullForm
of Plus[x, y]
) had its Head replaced.
As with Map
, Apply
transforms an expression and then evaluates it in entirety; if it is held evaluation does not continue:
Print @@@ Hold[a[1, 2], b[3, 4]]
Hold[Print[1, 2], Print[3, 4]]
One way to "release" this expression for evaluation is to replace Hold
with something else, like List
:
List @@ Print @@@ Hold[a[1, 2], b[3, 4]]
12
34
{Null, Null}
Note here that the elements 1
, 2
, and 3
, 4
are printed by Print
, but since Print[. . .]
itself evaluates to Null
the output of the line is {Null, Null}
.
Scan
Scan
is also like Map
, except that each individual expression which is wrapped in the specified head is evaluated outside of the main expression, and the main expression is never returned. If we Scan
a function without side-effects over an expression we get nothing:
Scan[f, head[1, 2, 3]]
(This could also be written f ~Scan~ head[1, 2, 3]
.) We do not even get an output line because the output is Null
and Mathematica does not print a lone Null
output line (by convention). If we use a function with side-effects, such as Print
, we get a different result:
Scan[Print, head[1, 2, 3]]
1
2
3
Crucially Scan
will carry out its evaluation even when an expression is held because it only looks at the subexpressions:
Print ~Scan~ Hold[a[1, 2], b[3, 4]]
a[1,2]
b[3,4]
Its evaluation can also be interrupted because, unlike Map
and Apply
which transform the entire expression, then evaluate it, Scan
works incrementally:
(Print[#]; If[# > 2, Return[]]) & ~Scan~ {1, 2, 3, 4, 5}
1
2
3
Because Scan
does not first duplicate and then modify the entire expression it may be more memory efficient than the use of Map
or Apply
, but it will still unpack packed arrays therefore if memory efficiency is a priority other methods may be preferred.
Conclusion
I hope these simple examples illustrate that these three functions, while closely related, have unique characteristics that differentiate their use, and each has powerful applications that the others do not.
Recommended reading regarding performance of these and related functions:
Notes on atomic objects
Not all expressions that have the appearance head[arguments]
in FullForm
are actually standard expressions in that form that can be manipulated with structural tools such as Apply
. Instead they have a special internal format and they are merely displayed in the form head[arguments]
.
From the documentation on Atomic Objects:
All expressions in Mathematica are ultimately made up from a small
number of basic or atomic types of objects.
These objects have heads that are symbols that can be thought of as
"tagging" their types. The objects contain "raw data", which can
usually be accessed only by functions specific to the particular type
of object. You can extract the head of the object using Head, but you
cannot directly extract any of its other parts.
Standard objects such as strings and integers are atomic, but so are other expressions that may not appear to be:
list = {1, "test", 1/2, 2 + 3 I};
list // FullForm
AtomQ /@ list
List[1, "test", Rational[1, 2], Complex[2, 3]]
{True, True, True, True}
Also atomic are more complex structures such as SparseArray
, Graph
, BooleanFunction
, and (in recent versions) Image
.
Atomic objects (or atoms) are handled differently from standard (compound) expression forms.
Rational[1, 2]
is atomic even though it appears otherwise.
Even though 1
is formatted without an obvious head, Head
will return Integer
.
(See Is there a summary of answers Head[] can give? for other examples.)
Apply
does not work in the "normal" way on atoms:
new @@@ list
{1, "test", 1/2, 2 + 3 I}
Replace
and kin often work on the apparent FullForm
of atoms:
Replace[list, head_[args__] :> {head, args}, {1}]
{1, "test", {Rational, 1, 2}, {Complex, 2, 3}}
Functions may be overloaded to handle atoms differently; e.g. many functions treat a SparseArray
as they would the Normal
form expression:
new @@ SparseArray[{1, 2, 3}]
new[1, 2, 3] (* not new[{1, 2, 3}] *)
Handling may appear somewhat irregular even within functions; Part
will "extract" (or return) the head of Rationa[1, 2]
but not anything else:
Part[1/2, 0]
Rational
Part[1/2, 1]
Part::partd: Part specification (1/2)[[1]] is longer than depth of object. >>
(1/2)[[1]]
(Somewhat related, and perhaps of interest: Head and everything except Head?)
f
when you do this, then it'll be much easier to understand what's going on....^2
threads over lists so your results don't reflect only the effect ofMap
, etc. but also this auto-threading. Take a look here and in the documentation page of each function. There are plenty of examples. For example the documentation clearly explains thatScan
is the same asMap
except it doesn't return anything. If is useful whenf
has a side-effect, e.g.f=Print
. $\endgroup$