21
$\begingroup$

I came across the following post by John Doty in this Google Groups discussion:

On Friday, January 11, 2013 8:23:16 PM UTC-7, amzoti wrote:

It is clear the Mathematica uses Lisp as one of the example programming paradigms it pulls from and I have a general question regarding this.

Perhaps not as much as you think. Mathematica is fundamentally a term rewriting system, a more general concept than the lambda calculus behind Lisp. To be sure, the easiest thing to express as term rewriting is the replacement of a function invocation by its body (as in the lambda calculus), but Mathematica can do more complex things, some rather strange from the lambda calculus point of view. Consider

f_[whoCalled]^:=f
Sin[whoCalled] 

which yields

Sin 

Very alien to Lisp!

I tried out the same code, and it does indeed return Sin for Sin[whoCalled]. But for Plus[whoCalled] it returns whoCalled.

Can someone please explain that little snippet of code, and why it sometimes returns the name of the caller and sometimes the symbol whoCalled? And what does the construct ^:= actually do?

EDIT

Having grokked around for a while, I notice something. When a function has the Flat attribute, the output is whocalled; when it does not, the output is the function name. So, for instance Map[whoCalled] returns Map but Times[whoCalled] returns whoCalled.

I have no idea if this is indeed the reason, but it's just something I have noticed.

$\endgroup$
  • 1
    $\begingroup$ For ^:= check the Documentation on UpSetDelayed $\endgroup$ – mikuszefski Dec 11 '14 at 8:39
  • 1
    $\begingroup$ The only thing I know is that the attribute isn't the reason, try Attributes@plus = Attributes@Plus; plus@whoCalled. BTW, this special upvalue is really interesting, it even breaks UpValues and Unset and Break! $\endgroup$ – xzczd Dec 11 '14 at 8:58
16
$\begingroup$

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

Mathematica graphics

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;
$\endgroup$
  • 1
    $\begingroup$ There's a weird thing happening with a mysterious If. In[9]:= ClearAll@a; a /: b_[c__, a[d_]] := a[Print[{b, {c}, d}]; b[c, d]]; {x, a[1]} During evaluation of In[9]:= {List,{x},1} During evaluation of In[9]:= {If,{False,a[{x,1}],a[{x,1}]},{x,1}} Out[9]= a[a[{x, 1}]] $\endgroup$ – Chip Hurst Nov 28 '15 at 18:09
  • 1
    $\begingroup$ If we give If a HoldAll attribute, we can see it's coming from formatting functions I believe. ClearAll@a; a /: b_[c__, a[d_]] := a[Print[Hold@{b, {c}, d}]; b[c, d]]; {x, a[1]} During evaluation of In[11]:= Hold[{List,{x},1}] During evaluation of In[11]:= Hold[{If,{!MemberQ[$BoxForms,StandardForm]||MatchQ[HoldComplete[a[{x,1}]],_[OutputSizeLimit`Dump`$unlimitedTextPattern]]||MatchQ[HoldComplete[a[{x,1}]], <<OMITTED FOR SPACE>>},{x,1}}] Out[12]= a[{x, 1}] $\endgroup$ – Chip Hurst Nov 28 '15 at 18:10
  • 1
    $\begingroup$ @xzczd It seems to be the same thing. Try something like a /: mem : b_[c__, a[d_]] /; (Print[HoldComplete[mem]]; True) := a[b[c, d]];, which is basically the same thing as I did for Kuba's. Basic the def. is applied once to generate a[{x, 1}]` and then again, when the internal code accidentally matches the definition in formatting the output boxes. Note the code if of the form If[t, e1, e2, a[{x , 1}], where e1 is also a[{x, 1}] but is matched in c__. $\endgroup$ – Michael E2 Nov 28 '15 at 18:56
  • 1
    $\begingroup$ Didn't get this the first time: i.stack.imgur.com/94zsb.png -- ah, figured it out: the extra HoldComplete[ToString[whoCalled]] etc. are from the floating popup info. Note having the whoCalled def. around messes up the Preferences dialog window! $\endgroup$ – Michael E2 Nov 28 '15 at 19:10
  • 1
    $\begingroup$ @xzczd Also note, running ./MathKernel from terminal, {x, a[1]} appears like you would expect. In[2]:= Clear@a; a /: b_[c__, a[d_]] := a[b[c, d]]; {x, a[1]} Out[2]= a[{x, 1}] $\endgroup$ – Chip Hurst Nov 28 '15 at 22:46
3
$\begingroup$

The difference in behaviour seem to be because Plus expects more than one argument.

f_[whoCalled] ^:= f
Sin[whoCalled]
Plus[whoCalled]
Minus[whoCalled]
MadeUp[whoCalled]

(* Sin
   whoCalled
   Minus
   MadeUp *)

f_[whoCalled, youCalled] ^:= f
Plus[whoCalled, youCalled]

(* Plus *)
$\endgroup$
  • 1
    $\begingroup$ I thought about that. But I noticed that If[whoCalled] returned If. $\endgroup$ – Shredderroy Dec 11 '14 at 9:13
  • 9
    $\begingroup$ Your example lead me to that: f_[___, whoCalled, ___] ^:= f; Plus[whoCalled] --> If O_o $\endgroup$ – Kuba Dec 11 '14 at 9:13
  • $\begingroup$ Lol, that's weird. $\endgroup$ – Chris Degnen Dec 11 '14 at 9:21
  • $\begingroup$ It also seems to be related to FE-Kernel communication because using only Kernel it returns whoCalled. $\endgroup$ – Kuba Dec 11 '14 at 10:39
  • 3
    $\begingroup$ @Kuba You're rewriting history: youtu.be/tbJwwJ33TEI?t=17s $\endgroup$ – Dr. belisarius Dec 11 '14 at 14:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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