# Implement settable properties

I am looking for advice on implementing the following. (It is possible that the answer is that it is simply not a good idea to do this.)

I would like to have an object similar to FittedModel. Let's call its head Obj.

Like FittedModel, it will have properties, e.g.

properties = {42}; (* properties stored here for the sake of this toy example *)

Obj[id_]["Property"] := properties[[id]]


Now let's define obj = Obj[1] and evaluating obj["Property"] will yield 42.

I am looking to make it possible to do obj["Property"] = 137 to set this property.

Here's an attempt that doesn't quite work (let's ignore SetDelayed for now and stick to Set):

Obj /: (Obj[id_]["Property"] = value_) := setObjProperty[id, value]

setObjProperty[id_, value_] := properties[[id]] = value


Now Obj[1]["Property"] = 137 will work as I want it to.

However, obj["Property"] = 256 will not. Instead of changing the property, it will associate the following definition with Obj: Obj[1]["Property"] = 256.

Question: Is there a way to implement this syntax for property setting in a reliable way? I realize that this isn't exactly in the spirit of Mathematica objects being immutable. In my case Obj represents a data structure implemented in C++ and id is a handle to it.

Note the following behaviour of = and :=:

The head of the expression to be set is evaluated by Set (or SetDelayed).

Graph has settable properties which are handled through SetProperty, PropertyValue, etc. I find this syntax very tedious, so I was looking for something simpler.

For my application having mutable state seems very natural, and I think it's not worth avoiding. I have a simulation that has a complex internal state, not fully exposed to Mathematica. The simulation can be stopped and resumed. Parameters can be adjusted when the simulation is stopped.

I think (I may be wrong) that in order to work only with immutable objects, in the natural Mathematica way, it would be necessary to expose the full simulation state to Mathematica and to store it as a pure Mathematica expression (instead of data on the C side). I would then store the full state as an Association, and have a function that runs the simulation and returns a new Association (simulation state), along with data collected during the simulation process. Associations would also give me this easy property-setting syntax for free.

However, exposing the full state may not be worth all the extra work.

-
Of course all this is not necessary at all to get the work done, it would just be nice to have :-) –  Szabolcs Jul 16 at 21:15

Well that's a hairy one. I like it though, as it forced me to think about aspects of evaluation that I am normally oblivious to. Unfortunately that thinking didn't lead to any great insights. My only idea so far is to interrupt evaluation and mess with the Stack as Leonid did for How do you set attributes on SubValues?
I have little experience in this area and I am sure to make a number of blunders before I figure it out, but nevertheless here is a first attempt as a proof of concept.

properties = {42};

Obj[id_]["Property"] := properties[[id]]

Obj /: (Obj[id_]["Property"] = value_) := setObjProperty[id, value]

setObjProperty[id_, value_] := properties[[id]] = value


_Obj :=
Block[{Obj},
Obj /: (Obj[id_]["Property"] = value_) := setObjProperty[id, value];
With[{set =
Cases[Stack[_], HoldForm[L_ = R_]?(FreeQ[#, Obj] &) :> ((# = R) &@L), 1, 1]},
Return[set[[1]], Set] /; set =!= {}
]
]


obj = Obj[1];

obj["Property"] = 256;

properties

{256}


I make no claim that this is in any way robust. Perhaps something better will come to me later.

-
Congrats on hitting 100k! –  Artes Jul 17 at 9:34
Thanks, @Artes :D –  Mr.Wizard Jul 17 at 9:36

How about the old Gayley-Villegas trick?

Obj /: (lhs_ = Obj[id_]) :=
Block[{$inSet = True}, lhs /: (lhs["Property"] = value_) := setObjProperty[id, value]; lhs /: Unset[lhs] := ClearAll[lhs]; lhs = Obj[id] ] /; ! TrueQ[$inSet]


Then we get the following behaviour:

obj = Obj[1];
UpValues[obj]

{HoldPattern[obj["Property"] = value$_] :> setObjProperty[1, value$]}

obj["Property"] = 42;
properties

{42}


It's a bit dirty though, but it might just be what you're after.

-
Interesting trick. But what if I do obj =. next? The up-value stays. –  Szabolcs Jul 16 at 22:00
@Szabolcs Well then we should also overload Unset in the Block ... let's see if I can update the answer. –  Teake Nutma Jul 16 at 22:02
@Szabolcs This is basically the same idea as what I used here (although not using VG). Didn't suggest it here precisely because of the hanging UpValues problem. +1. –  Leonid Shifrin Jul 16 at 22:44

UPD: Looks like I accidentally dismissed everything in the post below the horizontal line confusing it to comments, and tried to answered a more general question than needed. Too bad.

To clarify: the idea represented below is to have [still immutable] objects with pointers (in the form of symbols) to their properties inside the objects themselves, which makes it possible to extract pointers from expression anytime if needed.

For ease of use, however, I propose a way to define the pointers' structure unambiguously by the object structure; and instead of just placing pointers inside Hold, wrap them in Dynamic for a cleaner representation in the FrontEnd. In effect, properties become usual settable expressions.

It is possible that the answer is that it is simply not a good idea to do this.

It's probably true.

If you want to refer to object with properties as

Obj[id_]["Property"]


and want to Set

obj["Property"] = 137


then why not just

Obj[_]["Property"] := 137


whenever you need it?

From my modest but somewhat extensive experience, the only real reason one might need a flexible general complex mutable structure is for bulding complex graphical interfaces with minimal amount of boilerplate. This is also probably the only moment when one needs something like “object id”.

What I ended up with was looking like this:

(Note: Unevaluated and Holds are probably not needed)

setProperty // ClearAll

SetAttributes[setProperty, HoldFirst];

, HoldComplete[outerProperty_, innerProperties__]
, value_
, OptionsPattern[]] :=
, setProperty[outerProperty@inner
, HoldComplete@innerProperties
, value
, rest]

, HoldComplete@mostInnerProperty_Symbol
, value_
, OptionsPattern[]] :=
( expr = value;

, HoldComplete@mostInnerProperty_Symbol
, value_
, mostInnerProperty @
, rest] )


How this works:

setProperty[
"car"[1][
"engine"[power[], type[]]
, "driver"[name[], mood[]]]
, HoldComplete["engine", power]
, 250]

car[1][engine[power[500], type[]], driver[name[], mood[]]]


Of course, engine's power is only displayed as 500, by means of Dynamic. The real value is stored in a transparently structured expression:

setProperty[
"car"[1][
"engine"[power[], type[]]
, "driver"[name[], mood[]]]
, HoldComplete["engine", power]
, 500] // FullForm

"car"[1][
"engine"[power[Dynamic[power[Compose["engine", "car"[1]]]]], type[]]
, "driver"[name[], mood[]]]


Note that we've reset the value to 500.

Call it:

power@"engine"@"car"@1
500


A drawback: you have to use symbols for innermost keys. However, setProperty returns a complete object, with all its properties in it, and it is displayed nicely.

I haven't yet thought about possible applications for 2nd, 3rd, … arguments of Dynamic here.

When one can't afford a symbol for particular property's name, one may stick to strings, and use a universal symbolic head, like Property (Which would make references slightly more cumbersome, of course.), in which case the Dynamic wrapper probably becomes obsolete, unless one uses the technique to build complex interactive GUIs.

-
Regarding why I need an object ID, and why this is non-negotiable: the ID is a handle to a data structure implemented in C, not in Mathematica, and I'm also using managed library expressions. Obj[1] and Obj[2] refer to different data structures. –  Szabolcs Jul 17 at 15:47
@Szabolcs Oh, I didn't notice that in the initial post. :-( My answer seems to be completely useless then. –  Akater Jul 18 at 2:45
Well, it's not useless, it's interesting. I just can't apply it to my problem :-) In the end I'm using a syntax like ObjSet[obj, property -> value] to change the state of obj. –  Szabolcs Jul 18 at 2:59