I think the reason, at least primarily, that it works differently for Plus
is the following from its documentation:
- Unlike other functions, Plus applies built-in rules before user-defined ones.
It may seem a little obscure, because perhaps we don't know all the rules, but these two are mentioned explicitly:
- Plus[] is taken to be 0.
- Plus[x] is x.
Times
is similar.
According to these rules, Plus[whoCalled]
and Times[whoCalled]
evaluate to whoCalled
before the rule for whoCalled
is applied.
The behavior does not have to do with a symbol having the Flat
attribute per se, as can been seen in this example:
SetAttributes[g, Flat]
g[whoCalled]
(* g *)
Edit update: I had forgotten to address what UpSetDelayed
(^:=
) actually does. The principal explanations can be found in the tutorial Associating Definitions with Different Symbols and its related tutorials. What the definition does in this case is to create an up-value for the symbol whoCalled
, which is a rule for rewriting an expression, namely,
{HoldPattern[f_[whoCalled]] :> f}
This rule replaces an expression of the form h[whoCalled]
by its head h
. In the pattern, f_
causes f
to represent the head of the expression, which is the return value on the right-hand side.
End edit
Kuba's comment, showing Plus[whoCalled]
returning If
in his variant of whoCalled
, requires some explanation in light of the above. We can get a peak at what's going on in the following tweak of Kuba's definition:
f_[x___, whoCalled, y___] ^:= (Print[HoldComplete[f[x, whoCalled, y]]]; f);
Plus[whoCalled]
(*
HoldComplete[
If[False, whoCalled,
With[{OutputSizeLimit`Dump`boxes$ =
Block[{$RecursionLimit = Typeset`$RecursionLimit},
MakeBoxes[whoCalled, StandardForm]]},
OutputSizeLimit`Dump`loadSizeCountRules[];
If[TrueQ[BoxForm`SizeCount[OutputSizeLimit`Dump`boxes$, 1048576]],
OutputSizeLimit`Dump`boxes$,
OutputSizeLimit`Dump`encapsulateOutput[
whoCalled, $Line, $SessionID, 5]]], whoCalled]]
*)
(* If *)
So it's a side effect of the definition on some typesetting (output formatting).
To clarify, the internal code If[...]
has a form that accidentally matches the definition of whoCalled
:
If[x1, whoCalled, y1, y2]
with x1 = False
, y1 = With[]
, and y2 = whoCalled
, which symbol appears as both the second and fourth arguments to If
.
Indeed the definition wreaks havoc here and there throughout the system:
UpValues[whoCalled]
(* UpValues *)
? whoCalled
Definition not cleared:
Clear[whoCalled]
(* Clear *)
You can use a string to clear the definition:
ClearAll["whoCalled"]
The question is clearly just a toy for exploring how Mathematica evaluates expressions. If for some reason one wants to override functions in this indirect way, it might be good to spend some time thinking about which heads f
would be safe to override. I wonder if any System`
heads are safe. Clearly core programming ones like If
are not. One could write a function to verify a head is safe and guard against evaluating whoCalled
with unsafe f
like this:
ClearAll["whoCalled"]
(*safeHeadQ[h_] := Context[b] === "Global`"; (* more conservative alternative *) *)
safeHeadQ[h_] := MatchQ[h, Except[If]]; (* minimal for the OP example *)
f_[x___, whoCalled, y___] /; safeHeadQ[f] ^:= f;
^:=
check the Documentation on UpSetDelayed $\endgroup$Attributes@plus = Attributes@Plus; plus@whoCalled
. BTW, this special upvalue is really interesting, it even breaksUpValues
andUnset
andBreak
! $\endgroup$