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?
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?
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
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.
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.
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