Skip to main content
43 of 57
added 3 characters in body
faysou
  • 11k
  • 3
  • 50
  • 131

Using symbols to store data and object-like functions

Here are interesting functions to use symbols like objects. (I originally posted these thoughts in What is in your Mathematica tool bag?)

It is already well known that you can store data in symbols and quickly access them using DownValues.

(*Write/Update*)
mysymbol["property"]=2;
(*Access*)
mysymbol["property"]
(*Delete*)
Unset[mysymbol["property"]]

It is similar to a hashtable, new rules are added for each property to DownValues[mysymbol]. But internally, from what I understood, rules of a symbol are stored as a hashtable so that Mathematica can quickly find which one to use. The key ("property" in the example) doesn't need to be a string, it can be any expression (which can be used to cache expressions, as also shown in the post quoted above).

Keys

You can access the list of keys (or properties) of a symbol using these functions based on what dreeves once submitted (I was quite lucky to have found his post early in my Mathematica learning curve, because it allowed me to work on functions working with lots of different arguments, as you can pass the symbol containing the stored properties to a function and see which keys this symbol contains using Keys):

SetAttributes[RemoveHead, {HoldAll}];
RemoveHead[h_[args___]] := {args};
NKeys[_[symbol_Symbol]]:=NKeys[symbol]; (*for the head[object] case*)
NKeys[symbol_] := RemoveHead @@@ DownValues[symbol(*,Sort->False*)][[All,1]];
Keys[symbol_] := Replace[NKeys[symbol], {x_} :> x, {1}];

Usage example of Keys

a["b"]=2;
a["d"]=3;
Keys[a]

(*getting the values associated with the keys of the a symbol*)
a /@ Keys[a]

If you use multiple keys for indexing a value

b["b",1]=2;
b["d",2]=3;
Keys[b]

(*getting the values associated with the keys of the b symbol*)
b @@@ Keys[b]

PrintSymbol

I use this function a lot to display all infos contained in the DownValues of a symbol (which uses one key per value):

PrintSymbol[symbol_] :=
  Module[{symbolKeys=Keys[symbol]},
    TableForm@Transpose[{symbolKeys, symbol /@ symbolKeys}]
  ];

PrintSymbol[a]

Replacing a part of a list stored in a symbol

The following would produce an error

mysymbol["x"]={1,2};
mysymbol["x"][[1]]=2

One way to do this would be either to introduce a temporary variable for the list stored in mysymbol["x"], modify it and put it back in mysymbol["x"] or, if possible, use a syntax like

mysymbol["x"] = ReplacePart[mysymbol["x"], 1 -> 2]

Creation of objects with integrated functions

Finally here is a simple way to create a symbol that behaves like an object in object oriented programming, different function syntaxes are shown :

Options[NewObject]={y->2};
NewObject[OptionsPattern[]]:=
  Module[{newObject,aPrivate = 0},
    (*Stored in DownValues[newObject]*)
    newObject["y"]=OptionValue[y];
    newObject["list"] = {3, 2, 1};
    
    (*Stored in UpValues[newObject]*)
    function[newObject,x_] ^:= newObject["y"]+x;
    newObject /: newObject.function2[x_] := 2 newObject["y"]+x;

    (* "Redefining the LessEqual operator" *)
    LessEqual[newObject,object2_]^:=newObject["y"]<=object2["y"];

    (* "Redefining the Part operator" *)
    Part[newObject, part__] ^:= newObject["list"][[part]];

    (*Syntax stored in DownValues[newObject], could cause problems by 
      being considered as a property with Keys*)
    newObject@function3[x_] := 3 newObject["y"]+x;

    (*function accessing a "private" variable*)
    functionPrivate[newObject] ^:= aPrivate++;

    (* "Redefining the [ ] operator" *)
    newObject[x_] := x newObject["list"];

    (*Format*)
    Format[newObject,StandardForm]:="newObject with value y = "~~ToString[object["y"]];

    newObject
  ];

Properties are stored as DownValues and methods as delayed Upvalues (except for the [ ] redefinition also stored as DownValues) in the symbol created by Module that is returned. I found the syntax for function2 that is the usual OO-syntax for functions in Tree data structure in Mathematica.

Private variable

The variable aPrivate can be seen as a private variable as it is only seen by the functions of each newObject (you wouldn't see it using Keys). Such a function could be used to frequently update a list and avoid the issue of the previous paragraph.

If you wanted to DumpSave newObject you could know which aPrivate$xxx variable to also save by using the depends function of Leonid Shifrin described in the post Automatically generating a dependency graph of an arbitrary Mathematica function?.

depends[NewObject[]]

Note that xxx is equal to $ModuleNumber - 1 when this expression is evaluted inside Module so this information could be stored in newObject for later use.

Other way for storing functions in a different symbol

You could also store the function definition not in newObject but in a type symbol, so if NewObject returned type[newObject] instead of newObject you could define function and function2 like this outside of NewObject (and not inside) and have the same usage as before. See the second part of the post below for more on this.

(*Stored in UpValues[type]*)
function[type[object_], x_] ^:= object["y"] + x;
type /: type[object_].function2[x_] := 2 object["y"]+x;

(*Stored in SubValues[type]*)
type[object_]@function3[x_] := 3 object["y"]+x;

Usage example

x = NewObject[y -> 3]
x // FullForm

x["y"]=4
x@"y"

function[x, 4]
x.function2[5]
x@function3[6]

(*LessEqual redefinition test with Sort*)
z = NewObject[]
{x["y"],z["y"]}
l = Sort[{x,z}, LessEqual]
{l[[1]]["y"],l[[2]]["y"]}

(*Part redefinition test*)
x[[3]]

(*function accessing a "private" variable*)
functionPrivate[x]

(*[ ] redefinition test*)
x[4]

Reference/Extension

For a list of existing types of values each symbol has, see http://reference.wolfram.com/mathematica/tutorial/PatternsAndTransformationRules.html and http://www.verbeia.com/mathematica/tips/HTMLLinks/Tricks_Misc_4.html.

You can go further if you want to emulate object inheritance using a package called InheritRules available here http://library.wolfram.com/infocenter/MathSource/671/

Further ideas when storing functions in a head symbol

The idea is to use DownValues for storing properties in different symbols corresponding to objects and UpValues for storing methods in a unique head symbol (MyObject in the example below). We then use expressions of the form MyObject[object].

Here is a summary of what I currently use.

Constructor

Options[MyObject]={y->2};
MyObject[OptionsPattern[]]:=
   Module[{newObject,aPrivate = 0},
      newObject["y"]=OptionValue[y];
      newObject["list"] = {3, 2, 1};

      (*function accessing a "private" variable*)
      functionPrivate[MyObject[newObject]] ^:= aPrivate++;

      MyObject[newObject]
   ];

MyObject is used as "constructor" and as head of the returned object (for example MyObject[newObject$23]). This can be useful for writing functions that take into account the head of an object. For example

f[x_MyObject] := ...

Properties (like the value corresponding to the key "y") are stored as DownValues in a newObject symbol created by Module whereas functions will be stored in the MyObject symbol as UpValues.

Private variable

functionPrivate in the constructor definition has access to the "private" variable aPrivate. It is stored as UpValues of MyObject but with a dependency on each newObject symbol created in Module (which breaks the indendance between data stored in newObject and methods stored in MyObject that other function syntaxes described below share). It must be defined in Module contrary to other kind of functions described below, so that the symbols newObject and aPrivate generated in the same Module are stored in a same rule and thus can "see" each other.

Some methods stored as UpValues in MyObject (different syntaxes are shown)

(*Stored in UpValues[MyObject]*)
function[MyObject[object_], x_] ^:= object["y"] + x;
MyObject/: MyObject[object_].function2[x_] := 2 object["y"]+x;

(* "Redefining the LessEqual operator" *)
LessEqual[MyObject[object1_],MyObject[object2_]]^:=object1["y"]<=object2["y"];

(* "Redefining the Part operator" *)
Part[MyObject[object_], part__] ^:= object["list"][[part]];

myGet[MyObject[object_], key_] ^:= object[key];
mySet[MyObject[object_], key_, value_] ^:= (object[key]=value);    

Methods stored as SubValues in MyObject

A method stored to easily access the properties of an object. We restrict here key to be a string in order not to interfere with other functions defined as SubValues.

MyObject[object_Symbol][key_String] := object[key];

Another function stored in SubValues[MyObject]

MyObject[object_]@function3[x_] := 3 object["y"]+x;

Redefinition of the [ ] operator

MyObject[object_][x_] := x object["list"];

"Static" variable

Similarly to what is used for a private variable, a variable can be shared among all the objects of a similar class using a following definition for the function that accesses it. (Such variables use the keyword static in C++-like languages)

Module[{staticVariable=0},
   staticFunction[MyObject[object_]]^:=(staticVariable+=object["y"]);
]

Format

You can format the way the object is displayed with something like this:

Format[MyObject[object_Symbol],StandardForm]:="MyObject with value y = "~~ToString[object["y"]];

Creating an object

x = MyObject[y->3]

Test of the different functions

x // FullForm

function[x, 2]
x.function2[3]
x@function3[4]

x["y"]
x@"y"

(*LessEqual redefinition test with Sort*)
z = MyObject[]
{x["y"],z["y"]}
l = Sort[{x,z}, LessEqual]
{l[[1]]["y"],l[[2]]["y"]}

(*Part redefinition test*)
x[[3]]

(*function accessing a "private" variable*)
functionPrivate[x]

(*[ ] redefinition test*)
x[4]

(*static function example*)
staticFunction[x]
staticFunction[z]

Update properties

To update the "y" property of z you can use this (or use a setter function like mySet defined above)

ObjectSet[(_[symbol_Symbol]|symbol_),key_,value_]:=symbol[key]=value;
ObjectSet[z,"y",3]

If an object is of the kind MyObject[object] then value will be assigned to object[key] (DownValues of object) instead of being assigned to MyObject[object][key] (SubValues of MyObject whereas I want functions to be in general stored as UpValues of MyObject and properties as DownValues of object).

Another way that doesn't involve another function is to do

z[[1]]["y"] = 4

Update properties using Set

You can automate ObjectSet by overloading Set. This is what I use, as this makes the syntax more convenient for modifying the property of an object of the kind MyObject[object], but thouching to a function as important as Set is dangerous, especially if you're not the only user of your code, in this case use one of the method of the previous paragraph.

See this post for alternatives Alternative to overloading Set

enableAutomaticObjectSet[]:=
(
   Unprotect[Set];
   Set[symbol_[key_],value_] /; !TrueQ[$inObjectSet] :=
      Block[{$inObjectSet=True},
         ObjectSet[symbol,key,value]
      ];
   Protect[Set];
);

disableAutomaticObjectSet[]:=
(
   Unprotect[Set];
   DownValues[Set]={};
   Protect[Set];
);

So that you can do

enableAutomaticObjectSet[];
z["y"] = 4
function[z, 2]

This syntax works also for sub-objects

z["u"]=MyObject[]
z["u"]["i"]=2
PrintSymbol[z["u"]]
faysou
  • 11k
  • 3
  • 50
  • 131