10
$\begingroup$

I'm trying to figure out why these two pieces of code return different results. As expected,

f[Reap[Sow[x]]]

gives f[{x,{{x}}}] as output, but

fReap = f @* Reap;
fReap[Sow[x]]

gives f[{x,{}}]. According to my understanding of the composition operator @*, the expressions

h = f @* g; h[x]

and

f[g[x]]

should give the same result. What am I missing here?

$\endgroup$

1 Answer 1

10
$\begingroup$

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.

$\endgroup$
1
  • 1
    $\begingroup$ I've only just now realized that this behaviour in Composition is discussed at length in (54762). $\endgroup$
    – WReach
    Commented Apr 16, 2015 at 17:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.