22

To me, g /: f[g[x_]] := h[x] is just verbose equivalent of f[g[x_]] := h[x]. Can you raise an example that you have to use /:?

xzhu
  • 5,675
  • 4
  • 32
  • 52
  • 1
    It's got to do with where the definition is stored. Maybe you should read [Associating Definitions with Different Symbols](http://reference.wolfram.com/mathematica/tutorial/AssociatingDefinitionsWithDifferentSymbols.html) – Simon Jul 11 '11 at 10:13
  • 2
    Consider the help example `g /: g[x_] + g[y_] := gplus[x, y]` vs trying a transformation rule for every expression just to check if two `g[ ]` are added up – Dr. belisarius Jul 11 '11 at 11:13
  • Related MathGroups post: ["Tags, Upset, UpValues ..."](https://groups.google.com/group/comp.soft-sys.math.mathematica/msg/8bd4917ffa062f3e). – Alexey Popkov Aug 15 '11 at 06:16

3 Answers3

29

Actually, g /: f[g[x_]] := h[x] is not equivalent to f[g[x_]] := h[x]. The latter associates the definition with f, while TagSet (/:) and UpSet (^= and its delayed version, ^:=) associate the definition with g. This is a crucial difference and can be illustrated by a simple example. Let's say you want to have a set of variables that obey modulo 5 addition, i.e. 6 + 7 mod 5 = 3. So, we want anything with Head mod to behave correctly. Initially, we'd think that

a_mod + b_mod := mod@Mod[a + b, 5]

would work. But, it generates the error

SetDelayed::write : Tag Plus in a_mod + b_mod is Protected.

We could remove Unprotect Plus and our definition would then work, but this may cause problems with other definitions and as Plus accumulates more definitions, it would slow down. Alternatively, we can associate the addition property with the mod object itself via TagSet

mod /: mod[a_] + mod[b_] := mod @ Mod[a + b, 5]

or UpSetDelayed

mod[a_] + mod[b_] ^:= mod @ Mod[a + b, 5]

Setting an upvalue is somewhat more correct from a conceptual point of view since mod is the one with the different property.

There are a couple of issues to be aware of. First, the upvalue mechanism can only scan one level deep, i.e. Plus[a_mod, b_mod] is fine, but Exp[Plus[a_mod, b_mod]] will throw an error. This may require you to get creative with an intermediate type. Secondly, from a coding perspective UpSetDelayed is easier to write, but occasionally there is some ambiguity as to which Head is the upvalue associated with. TagSet handles that by explicitly naming the appropriate Head, and in general, it is what I tend to prefer over UpSet.

Some of Mathematica's operators do not have any behavior associated with them, so they're not protected. For these operators, you can define functions as you wish. For instance, I've defined

a_ \[CircleTimes] b_ := KroneckerProduct[a,b]
a_ \[CircleTimes] b_ \[CircleTimes] c__ := a \[CircleTimes] ( b \[CircleTimes] c )

and

a_ \[CirclePlus] b__ := BlockDiagonal[{a,b}]

to provide convenient shorthand notations for matrix operations that I use a lot.

My example above was a little contrived, but there are a number of times UpValues have come in handy. For example, I found that I needed a symbolic form for the complex roots of unity that behaved appropriately under multiplication and exponentiation.

Example: A straightforward and useful example is marking a Symbol as Real:

makeReal[a__Symbol] := (
     # /: Element[#, Reals] := True; 
     # /: Im[#] := 0; 
     # /: Re[#] := #;
     # /: Abs[#] := Sign[#] #;
     # /: Arg[#] := Piecewise[{{0, Sign[#] >= 0}, {Pi, Sign[#] < 0}}]
   ) & /@ List[a]

Note the use of TagSet as Element[ a, Reals ] ^:= True would be ambiguous. What would the rule be attached to a or Reals? Also, if we wanted a positive real number, we could set Arg[#]:=0 which allows Simplify to behave as expected, e.g. Simplify[Sqrt[a^2]] == a.

rcollyer
  • 10,475
  • 4
  • 48
  • 75
  • 1
    Shouldn't the example be something like `mod /: a_mod + b_mod := mod@Mod[Delete[a, 0] + Delete[b, 0], 5]`? I'm getting recursion errors otherwise. Maybe I'm missing something, trying to wrap my head around upserts. – Gleno Jan 13 '20 at 19:08
  • 1
    Or better `mod /: a_mod + b_mod := mod@Mod[Identity@@a + Identity@@b, 5]` – Gleno Jan 13 '20 at 19:30
  • 1
    @gleno good catch. I think the better alternative is to use `mod[a_] + mod[b_]`, much simpler. At the time, I think I was enamored with using the `Head` in the pattern (which is till neat), but it is less useful if you need to use the internals. – rcollyer Jan 13 '20 at 19:42
19

In addition to the excellent answer by @rcollyer, I'd like to emphasize a few other important things about UpValues.

Soft/local redefinition of system and other functions

One very important aspect is that they allow you to "softly" overload some system functions only on certain symbols. The importance of this was pointed out by @rcollyer, but can not be emphasized enough - this makes the effect of your code local, and drastically reduces the chances that your code can globally interact and affect some other part of the system or other piece of user-defined code, unlike when you Unprotect system symbols and add some DownValues to them.

In addition to being safe and local, such redefinitions may also be quite general, if one uses constructs like yourSymbol/:f_[_yourSymbol,rest___]:=.... These should be used with care, but can sometimes give very concise and simple solutions. Here is one example where one code can be used to "overload" several system functions at once, giving them additional non-trivial functionality.

Order of evaluation

The next point is evaluation. The common statement you can encounter is that "UpValues are applied before DownValues". This must be clarified: for f[g[args]] it means that UpValues for g are applied before DownValues for f, provided that the evaluation process already went all they way "down" to innermost parts, and then went back "up". In particular, it does not mean that UpValues for g will be applied before DownValues for g - if g[args] can evaluate inside f because g has appropriate DownValues, it will (unless f has one of the Hold-attributes), and the presence of UpValues won't prevent that, because (for standard evaluation), evaluation of g[args] happens before the evaluation of f[result-of-evaluation-of g[args]]. For example, here:

In[58]:= 
ClearAll[f, g];
f[x_] := x^2;
g /: f[g[x_]] := Sin[g[x]];
g[x_] := Cos[x];

In[62]:= f[g[y]]
Out[62]= Cos[y]^2

The UpValues for g had no chance to apply, since g[y] is transformed into Cos[y] at the previous evaluation step. The situation would be different for non-standard evaluation - either if we give f attributes HoldAll or HoldFirst, or if we wrap g[y] in Unevaluated - in both cases we give the evaluator the instruction to skip the evaluation of g[y]:

In[63]:= f[Unevaluated[g[y]]]

Out[63]= Sin[Cos[y]]

Escaping Hold-attributes

This one is related to the previous point: one should be aware that search for UpValues is performed even inside heads with Hold- attributes, and therefore, UpValue-based definitions may evaluate even when similarly-looking DownValue - based ones won't. Example:

In[64]:= ClearAll[f,ff];
f[x_]:=Print["Evaluated"];
ff/:h_[ff[x_]]:=Print["Evaluated"];

In[67]:= Hold[f[1]]
Out[67]= Hold[f[1]]

In[68]:= Hold[ff[1]]
During evaluation of In[68]:= Evaluated

If one wants to absolutely prevent the search for UpValues, one should give a function the HoldAllComplete attribute. For example:

In[69]:= {HoldComplete[f[1]],HoldComplete[ff[1]]}
Out[69]= {HoldComplete[f[1]],HoldComplete[ff[1]]}

Level-1 tag depth restriction

This was already mentioned by @rcollyer. This limitation was introduced for efficiency of the pattern-matcher/evaluator. I just want to stress one important and rather non-obvious consequence of it: it looks like you can not use UpValues to overload assignment (Set operator) so that it would work on variables assigned to objects of some specific type you introduce. Here is an attempt:

In[74]:= 
ClearAll[a,myType,myCustomCode,newValue];
myType/:Set[var_myType,rhs_]:=myCustomCode;

This seems to work. But let us try:

In[79]:= a = myType[1, 2, 3];
a = newValue;
a

Out[81]= newValue

It does not do what we want, obviously. The problem is that Set holds its l.h.s., so by the time the pattern-matching happens, it only has the symbol a, not its value. And because we can not associate the definition with tags deeper than on the first level of the expression, the following won't work either:

ClearAll[a,myType,myCustomCode,newValue];
myType/:Set[var_,rhs_]/;MatchQ[var,_myType]:=myCustomCode;
TagSetDelayed::tagpos: Tag myType in (var_=rhs_)/;MatchQ[var,_myType]
  is too deep for an assigned rule to be found. >>

To my knowledge, UpValues can not be used to solve this problem, which is a pity, since having a usual = syntax with custom assignment code for various data types would be convenient. For a similar discussion, see e.g. this post. This situation is not unique for Set - the same would hold for any function that holds the argument that you want to use for your UpValue-based definition.

Some differences between UpSet and TagSet, UpSetDelayed and TagSetDelayed

It is worth knowing that when you use UpSet or UpSetDelayed, then all tags at level 1 acquire additional definitions (rules). For example:

Clear[a,b];
Plus[a,b]^:=1;

?a
Global`a
a/:a+b:=1

?b
Global`b
b/:a+b:=1

In contrast with this, TagSet and TagSetDelayed are more precise:

ClearAll[a,b];
a/:Plus[a,b]:=1;

?a
Global`a
a/:a+b:=1

?b
Global`b

In my experience, the latter behavior is usually more desirable, so in most cases I prefer TagSet or TagSetDelayed over UpSet or UpSetDelayed.

Community
  • 1
  • 1
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • +1, like always and for reminding me that `TagSet` and `TagSetDelayed` are ternary operators. – rcollyer Jul 13 '11 at 19:52
  • while it doesn't work quite as expected, I have been able to overload `SetDelayed` [previously](http://stackoverflow.com/questions/4174791/preventing-avalanche-of-runtime-errors-in-mathematica/4176381#4176381). Ah, I see why, my specifically uses symbols, not their values, so the restriction does not hold. – rcollyer Aug 17 '11 at 13:56
  • 1
    @rcollyer You can overload `SetDelyaed` on explicit symbols with `UpValues`, that's true. I did not fully explore this possibility myself, but it may well be that this can be used constructively and in interesting ways. This recent answer by @WReach comes to mind as one such example: http://stackoverflow.com/questions/6917656/possible-to-block-ownvalues-when-downvalues-already-exist/6919530#6919530 – Leonid Shifrin Aug 17 '11 at 14:40
  • I had not looked at that question. Interesting method. – rcollyer Aug 17 '11 at 15:01
  • @Leonid Re: order of evaluation when evaluating f[g[arg]] the order is: downvalues of g, upvalues of g, downvalues of f . Downvalues of g are evaluated BEFORE upvalues of g. However if you look at ?g you will see that upvalues are listed before downvalues. So the presentation order is somewhat misleading. Do you agree? – magma Sep 15 '11 at 13:25
  • @Leonid by the way: where is it written in the documentation that downvalues[g] are evaluates before upvalues[g]? I know it is correct, but I cannot find it written anywhere explicitly. At least not in the obvious places. – magma Sep 15 '11 at 14:50
  • @magma The thing is that `DownValues` for `g` apply on the "previous evaluation cycle", w.r.t. `UpValues` for `g`. When we read about standard evaluation sequence, we see that first the heads are evaluated, then parts (recursively, evaluation "goes down" the expression tree), then evaluation goes "up", so that attributes are used, and finally rules are applied. For `f[g[x]]`, at this step first `UpValues` for `g` apply, then `DownValues` for `f`. But, `g[x]` was evaluated (and `DownValues[g]` applied) during the previous recursive step of (sub) part evalution - I call it "previous cycle". – Leonid Shifrin Sep 15 '11 at 15:14
9

Rcollyer has already given an excellent answer but here is an example of when you might use UpValues: when you are defining a particular data structure, with its own Head, and you want to defining how built in operations like arithmetic work with that structure. I once did this for a timeSeries data structure where, for example, addition would match up the dates in the first columns and add the corresponding pairs of values in the second columns. Adding T * 2 vectors with dates in the first column would give nonsense dates if you hadn't defined such an operation using an UpValue.

Verbeia
  • 4,400
  • 2
  • 23
  • 44
  • I've used `makeReal` often enough. I normally have to rewrite it every time, though, since it is trivial. But, more usefully, I've done the same thing you have as a program I work with produces spin densities and I wanted total density (spin-up + spin-dn) and magnetization (spin-up - spin-dn), and the object representation wasn't simple. – rcollyer Jul 12 '11 at 04:16