5

I have read the article by Leonid on the question. I am still struggling. I am trying to use Association to inject some pseudo-typing in my code. Here is my attempt:

TSInitialise[name_String] := 
 DataStructure @@ {"Name" -> name, "UpToDate" -> True}
rTS[ts_DataStructure] := Association @@ ts;
Name[ts_DataStructure] := rTS[ts][["Name"]];

so that:

TSTest = TSInitialise["Andrei" ]

yields:

DataStructure["Name" -> "Andrei", "UpToDate" -> True]

Now to overcome the immutability issue, I find myself writing a lot of boiler-plate code similar to:

ToTS[ass_Association] := DataStructure @@ (Normal@ass);
wUpToDate[ts_DataStructure, updQ_: False] := Module[{tsLoc},
   tsLoc = rTS[ts];
   tsLoc[[Key["UpToDate"]]] = updQ;
   ToTS[tsLoc]
   ];
wName[ts_DataStructure, name_: "Milutin"] := Module[{tsLoc},
   tsLoc = rTS[ts];
   tsLoc[[Key["Name"]]] = name;
   ToTS[tsLoc]
   ];

forcing me to set the datastructure over and over again:

TSTest = wUpToDate[TSTest]
TSTest = wName[TSTest, "Milutin"]

yield:

DataStructure["Name" -> "Andrei", "UpToDate" -> False]
DataStructure["Name" -> "Milutin", "UpToDate" -> False]

Am I missing something ?

Trad Dog
  • 450
  • 4
  • 9

1 Answers1

8

My understanding is that you want to wrap an association into an inert wrapper representing the type.

Immutable version

Then, you should do just that:

TSInitialise[name_String] := DataStructure[<|"Name" -> name, "UpToDate" -> True|>]
Name[DataStructure[assoc_]] := assoc["Name"];

So that

TSTest = TSInitialise["Andrei"]

(* DataStructure[<|"Name" -> "Andrei", "UpToDate" -> True|>] *)

In this version, the data structure carries no mutable state. So, modifications produce a brand new immutable structure. This is easy too, particularly if you don't care about the order of the keys:

ClearAll[dsUpdate, wUpToDate, wName]
dsUpdate[DataStructure[assoc_], k_ -> v_] := DataStructure[Append[assoc, k -> v]];
wUpToDate[ds_DataStructure, updQ_: False] := dsUpdate[ds, "UpToDate" -> updQ];
wName[ds_DataStructure, name_: "Milutin"] := dsUpdate[ds, "Name" -> name];

so that

TSTest = wUpToDate[TSTest]
TSTest = wName[TSTest, "Milutin"]

(* DataStructure[<|"Name" -> "Andrei", "UpToDate" -> False|>] *)

(* DataStructure[<|"UpToDate" -> False, "Name" -> "Milutin"|>] *)

Mutable version

One way to introduce mutability is to make your type container HoldAll, and store a unique symbol inside it. That symbol will be assignable, and will represent the mutable state of your objects:

ClearAll[DataStructureM, TSInitialiseM]
SetAttributes[DataStructureM, HoldAll];
TSInitialiseM[name_String] :=
  Module[{assoc = <|"Name" -> name, "UpToDate" -> True|>},
    DataStructureM[assoc]
  ];
Name[DataStructureM[assoc_]] := assoc["Name"];
DataStructureM /: Normal[DataStructureM[assoc_]] := assoc;

Modifications are also easy to implement in this approach. They will now modify the mutable state of the object passed to them.

ClearAll[dsUpdate, wUpToDate, wName]
dsUpdate[ds : DataStructureM[assoc_], k_ -> v_] :=
  Module[{},
    assoc[k] = v;
    ds
  ];
wUpToDate[ds_DataStructureM, updQ_: False] := dsUpdate[ds, "UpToDate" -> updQ];
wName[ds_DataStructureM, name_: "Milutin"] := dsUpdate[ds, "Name" -> name];

So, in contrast to the immutable case, they will not produce the new immutable objects, but will change the state of the original one:

(testM = TSInitialiseM["Andrei"]) // Normal
wUpToDate[testM];
testM // Normal
wName[testM];
testM // Normal

(* 

  <|"Name" -> "Andrei", "UpToDate" -> True|>

  <|"Name" -> "Andrei", "UpToDate" -> False|>

  <|"Name" -> "Milutin", "UpToDate" -> False|>

*)

So, the above output represents the state of the same object in 3 different points in time, while for the immutable case, we had produced 3 different immutable objects.


Which one to use depends on the problem you are solving. You should just keep in mind that using mutable objects in Mathematica should always be a conscious choice, since doing so you lose many advantages of immutability (particularly in the context of Mathematica which has been optimized for this), and get all the problems associated with manipuations with mutable state. There are cases where it is worth it, but I wouldn't say they are typical.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • Hi Leonid, this is indeed much more elegant. Thanks a lot. Trad. – Trad Dog Mar 18 '16 at 08:29
  • Regarding the mutability in this case. The first remark is that you are using Append where I was trying with AssociateTo, which I realise does not respect immutability. What would be the right approach if one wanted to make use of AssociateTo in your function dsUpdate (a mutable version of it) ? With the similar aim in mind, to modify the same structure, one needs to re-assign systematically the initial object (TSTest in this case). I was trying to think of a shortcut to this operation, to no avail. Any insight ? There might be good reasons not to do it. Best, Trad. – Trad Dog Mar 18 '16 at 08:44
  • 1
    @TradDog I have added a mutable version to my answer. – Leonid Shifrin Mar 18 '16 at 19:15
  • Leonid, having the two versions is enlightening. The use of HoldAll, in particular, was what I was missing in my mutable attempts. I take in your caveats on Mutability though. It is indeed a more native Mathematica way to code, although it does add sometimes its share of ancillary code. Thanks a lot for sharing yet another way of explaining the concepts and how to best implement them. Best. – Trad Dog Mar 21 '16 at 09:20
  • @TradDog Good to know that you found it useful. I actually like this example, it allows to explain the difference in the two approaches in the most simple setting. – Leonid Shifrin Mar 21 '16 at 20:46