Reap
has the attribute HoldAll
. This is necessary so that its argument is not evaluated until Reap
has set up the conditions necessary to capture the values designated by Sow
. The implementation of @*
(Composition
) is such that it interferes with the attributes of the right-most composed function. In the exhibited example, the Sow
expression is evaluated prematurely because Composition
interferes with the HoldAll
attribute on Reap
.
Analysis
This behaviour will effect any function with attributes, not just Reap
. The following examples show how Composition
effectively strips the HoldAll
attribute off of Hold
...
f @ Hold[1 + 1]
(* f[Hold[1 + 1]] *)
(f @* Hold)[1 + 1]
(* f[Hold[2]] *)
Composition[f, Hold][1 + 1]
(* f[Hold[2]] *)
... and here is an example showing SequenceHold
being ignored:
SetAttributes[ss, SequenceHold]
Defer @ ss[Sequence[1, 2, 3]]
(* ss[Sequence[1, 2, 3]] *)
(Defer @* ss)[Sequence[1, 2, 3]]
(* ss[1, 2, 3] *)
The problem is that f @* Hold
is behaving as if it were implemented as f @ Hold[##] &
:
f @ Hold[1 + 1]
(* f[Hold[1 + 1]] *)
Composition[f, Hold][1 + 1]
(* f[Hold[2]] *)
(f @ Hold[##] &)[1 + 1]
(* f[Hold[2]] *)
The expression f @ Hold[##] &
is a pure function that has no attributes. In particular, it does not have the HoldAll
attribute. Thus, it evaluates its argument before passing it on to the body of the function. Just like Composition
. And, also like Composition
, functions such as these preserve the attributes of all functions but the right-most:
ClearAll[ff, gg]
SetAttributes[{ff, gg}, HoldAll]
ff[x_] := Defer[22 + x]
gg[x_] := 11 + x
ff @ gg[1 + 1]
(* 22 + gg[1 + 1] *)
Composition[ff, gg][1 + 1]
(* 22 + gg[2] *)
(ff @ gg[##] &)[1 + 1]
(* 22 + gg[2] *)
On an incidental note, degenerate cases involving Composition
actually do preserve function attributes. Consider:
Composition[Hold][1 + 1]
(* Hold[1 + 1] *)
Composition[Identity, Hold][1 + 1]
(* Hold[1 + 1] *)
Indeed, optimizations involving Identity
sometimes go too far, getting the wrong answer:
Hold @ Identity[1 + 1]
(* Hold[Identity[1 + 1]] *)
Composition[Hold, Identity][1 + 1]
(* Hold[1 + 1] *)
But we digress. To work around the main problems under discussion, we can implement our own composition using a Function
with appropriate attributes. For example, we can preserve HoldAll
like this:
Function[Null, f @ Hold[##], HoldAll][1 + 1]
(* f[Hold[1 + 1]] *)
General Workaround
We could also define our own general composition operator that makes a pretty good attempt at preserving attributes:
comp[f___] :=
Module[{h}
, SetAttributes[h, HoldAll]
; ReplacePart[
Function[Null, Null, HoldAllComplete]
, h[f, ##] //. h[a___, b_, c_] :> h[a, b@c]
, 2
, 1
]
]
This attempt is not fool-proof, but in practice it is usually good enough (and better than Composition
):
comp[f, Hold]
(* Function[Null, f[Hold[##1]], HoldAllComplete] *)
comp[f, Hold][1 + 1]
(* f[Hold[1 + 1]] *)
comp[Defer, ss][Sequence[1, 2, 3]]
(* ss[Sequence[1, 2, 3]] *)
comp[ff, gg][1 + 1]
(* 22 + gg[1 + 1] *)
comp[Hold][1 + 1]
(* Hold[1 + 1] *)
comp[Identity, Hold][1 + 1]
(* Hold[1 + 1] *)
comp[Hold, Identity][1 + 1]
(* Hold[Identity[1 + 1]] *)
It would be better if Composition
were built into the Mathematica kernel in such fashion as to respect attributes. However, that would represent a breaking change to a long-standing language feature.