Oh yes. UpValues
are used quite a bit. There are several common uses, and you may have a look at this and especially this question and answers therein to see some sample uses.
As for practical uses: I will just mention a couple of examples for what I consider to be the main practical use: overloading functions (system or user-defined) on custom data types, so that such redefinitions are local (in the sense that they are attached to the heads representing the new data type, rather than to the functions being overloaded).
One example is my implementation of the large data framework, where it would not be an exaggeration to say that UpValues
were crucial element of it. I routinely and automatically create thousands of them during the operation of the framework, and they serve as a powerful encapsulation mechanism, which allowed me to use the OOP-style encapsulation in a very effective way. I will reproduce here the main function using them:
ClearAll[definePartAPI];
definePartAPI[s_Symbol, part_Integer, dir_String] :=
LetL[{sym = Unique[], hash = Hash[sym],
fname = $fileNameFunction[dir, hash]
},
sym := sym = $uncompressFunction@$importFunction[fname];
s /: HoldPattern[Part[s, part]] := sym;
(* Release memory and renew for next reuse *)
s /: releasePart[s, part] :=
Replace[Hold[$uncompressFunction@$importFunction[fname]],
Hold[def_] :> (ClearAll[sym]; sym := sym = def)];
(* Check if on disk *)
s /: savedOnDisk[s, part] := FileExistsQ[fname];
(* remove from disk *)
s /: removePartOnDisk[s, part] := DeleteFile[fname];
(* save new on disk *)
s /: savePartOnDisk[s, part, value_] :=
$exportFunction[fname, $compressFunction @value];
(* Set a given part to a new value *)
If[! TrueQ[setPartDefined[s]],
s /: setPart[s, pt_, value_] :=
Module[{},
savePartOnDisk[s, pt, value];
releasePart[s, pt];
value
];
s /: setPartDefined[s] = True;
];
(* Release the API for this part. Irreversible *)
s /: releaseAPI[s, part] := Remove[sym];
];
What this does is to define a certain API for a symbol which represents the list in the framework. For lists of thousands parts, many thousands such UpValues
are created. They are saved (serialized) when the list representation is saved for a later use, and read back in when this representation is loaded from disk. This is by far the most massive use of UpValues
in my practice at least, and UpValues
played a major role in my ability to structure the code this way, providing necessary means for encapsulation, instantiation and separation of interface and implementation. You can find more details in the linked discussion of the framework.
Another, somewhat similar, example is that of an implementation of mutable data structures in Mathematica. The way I do it is described here, and I will again use some code from that post to illustrate the point:
Module[{parent, children, value},
children[_] := {};
value[_] := Null;
node /: new[node[]] := node[Unique[]];
node /: node[tag_].getChildren[] := children[tag];
node /: node[tag_].addChild[child_node, index_] :=
children[tag] = Insert[children[tag], child, index];
node /: node[tag_].removeChild[index_] :=
children[tag] = Delete[children[tag], index];
node /: node[tag_].getChild[index_] := children[tag][[index]];
node /: node[tag_].getValue[] := value[tag];
node /: node[tag_].setValue[val_] := value[tag] = val;
];
The node
represents a new data type (tree node), and using UpValues
allowed me to use the familiar dotted notation without hard overloading of Dot
, and without giving the API symbols (addChild
, getChild
, etc) any meaning, so they can be reused by other data types.
There are many more uses for UpValues
, but what I want to stress is that they are a very practical tool.
UpValues
. See my question $\endgroup$