7
$\begingroup$

I know I can use the Property interface for things like Graph but I want to use it for all of my objects. This includes:

Can I define it in some efficient, non-memory-leaky way for any object I want?

$\endgroup$
7
$\begingroup$

The answer is a definitive yes, if you're willing to work with the new Language`ExpressionStore. This goes hand-in-hand with the Language`MutationHandler stuff to allow us to finally do custom OOP in Mathematica without any odd tricks.

if you just want to play with this scroll down to the example section

Basic Idea

But for the problem at hand, this is actually very simple. We simply make property-store:

$propStore=Language`NewExpressionStore["<PropertyStore>"];

Then we use the get-put-remove interface on this:

containsQ[x_]:=
  $propStore@"containsQ"[x];
containsQ[x_, p_]:=
  $propStore@"containsQ"[x, p];
(* ::Subsubsection::Closed:: *)
(*get*)
get[x_, p_]:=
  $propStore@"get"[x, p];
(* ::Subsubsection::Closed:: *)
(*set*)
set[x_, p_, v_]:=
  $propStore@"put"[x, p ,v];
(* ::Subsubsection::Closed:: *)
(*remove*)
remove[x_]:=
  $propStore@"remove"[x];
(* ::Subsubsection::Closed:: *)
(*keys*)
keys[]:=
  $propStore@"getKeys"[];
keys[x_]:=
  $propStore@"getKeys"[x];
(* ::Subsubsection::Closed:: *)
(*list*)
list[]:=
  $propStore@"listTable"[];

And now we can use this naturally as a property set/get mechanism.

Fun Example

I put this all into a little package that imitates the syntax of Property and friends. Load this from GitHub:

Get["https://github.com/b3m2a1/mathematica-tools/raw/master/Props.m"]

then we'll define a function that caches results in a memory cheap way:

cachedCompute[m_, p_, fn_] :=
 Replace[PropVal[m, p], 
  Missing["PropertyAbsent", p] :>
   With[{val = fn[m]}, 
    SetProp[m, p -> val];
    val
    ]
  ]

Module[{},
  (* I  do this to avoid $HistoryLength *)
  m1 = MemoryInUse[];
  myMat = RandomReal[{}, {100, 100}];
  m1
  ];

t1 = RepeatedTiming[cachedCompute[myMat, "Inverse", Inverse]][[1]];
t2 = RepeatedTiming[Inverse[myMat]][[1]];
m2 = MemoryInUse[];

t2/t1

5.*10^1

m2 - m1

167088

Clear@myMat

MemoryInUse[] - m1

10480

And we see the caching got us a 50x speed-up but once the original matrix was cleared, the cached memory was free.

Fast properties without memory leaks, thanks to Jason B.

Direct Overloading

We can also make use of this to do some direct overloading for our own type, e.g.:

myObj /: PropertyValue[obj_myObj, key_] :=
  PropVal[obj, key];
myObj /: SetProperty[obj_myObj, p : (Rule | RuleDelayed)[key_, val_]] :=

  SetProp[obj, p];
myObj /: RemoveProperty[obj_myObj, key_] :=
  RemoveProp[obj, key];
myObj /: PropertyList[obj_myObj] :=
  PropList[obj];

Now it's pretty easy to work with:

o = myObj[];

PropertyValue[o, "test"]

Missing["PropertyAbsent", "test"]

SetProperty[o, "test" :> RandomReal[]]

PropertyValue[o, "test"]

0.131471

PropertyValue[o, "test"]

0.882595

RemoveProperty[o, "test"]

Props`Private`$$hold[RandomReal[]] (* just a byproduct of how Hold-ing is done in the package *)

Note that this is happening on the Expression itself and not the Symbol:

With[{o = o},
 SetProperty[o, "test" :> RandomReal[]];
 PropertyValue[o, "test"]
 ]

0.170485
$\endgroup$

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.