5
$\begingroup$

Parts of Associations can be set with the following syntax:

as = <|"a" -> 10|>

<|"a" -> 10|>

as["b"] = 20;
as

<|"a" -> 10, "b" -> 20|>

This works since as is a symbol. But doesn't work when an Association is assigned to an expression, ie using SubValues - which is a convenient way to organize projects around a common Head instead of using contexts, ie,

myProject[as1] = <|"a"->10|>

<|"a" -> 10|>

myProject[as1]["b"]= 20 

Association::setps: <|a->30|> in the part assignment is not a symbol.

But assignments to expressions is not otherwise a problem, eg:

myProject[foo][bar] = 40

40

Is this a limitation of the Association implementation or is there a fundamental problem in such an extension?

EDIT

I'm aware of alternatives such as Append (or AppendTo)

myProject[as1] // Append["c" -> 3]    (* can substitute an existing Key eg "a"->1 to overwrite *)

But not being able to use Set is upsetting, as it seems to break a pattern. What's the underlying issue why the interpreter can't evaluate the SubValue expression?

$\endgroup$
  • $\begingroup$ Comments are not for extended discussion; this conversation has been moved to chat. $\endgroup$ – Kuba Feb 17 '18 at 23:58
  • $\begingroup$ Related: (7214) $\endgroup$ – Mr.Wizard Feb 18 '18 at 20:08
  • $\begingroup$ @Kuba, what's the criterion for extended discussion to be moved and why isn't it applied evenly? There are active posts (such as an answer to this: goo.gl/ffB7Ft) with even longer comment threads. Is it the number of participants? $\endgroup$ – alancalvitti Feb 21 '18 at 22:31
  • $\begingroup$ @alancalvitti There isn't any hard limit. And it varies from site to site, we are very liberal about that I'd say. You won't find so many comments in math.se etc. I am probably the only one from mods who deletes comments on daily basis, that is why it may seem uneven/unexpected. Don't have time to read all affected topics obviously :) I'm trying to not delete comments that add value/fun etc, but if they are a conversation about clarifying the question then they should be summed up as an edit and removed, the format of se is clear question -> clear answer, comment should not matter. $\endgroup$ – Kuba Feb 22 '18 at 8:15
  • $\begingroup$ @alancalvitti This is how I 'judge' comments, more or less. Hope that helps, if you don't agree, have other doubts, feel free to ask a general question on meta. $\endgroup$ – Kuba Feb 22 '18 at 8:16
5
$\begingroup$

What I'm proposing here is that the OP can solve his underlying problem, which is overloading symbols by qualifying them. He proposes to sub-values as a qualifying mechanism, but has encountered a problem with that approach when associations are involved.

My counter proposal is that using context qualified symbols will provide a simple and elegant way to do everything the OP wants to do with sub-values, including adding a new key-value pair to an association with Set (where sub-values failed)

In a comment now removed from the question, the OP objected to use of the context construct because writing Begin-End frames was "so 20th century". However, a symbol can always be used as a context qualifier without writing and evaluating Begin and End frames. To illustrate this I will give a rather contrived example where the context debug`is used to control some debugging output.

Global variables. I define these just to show they are not be affected by changes to debug`clicks and debug`on,

clicks = 0; on = 42;

Commands to turn debugging on and off.

debugOn := (debug`clicks = 0; debug`on = True)
debugOff := debug`on = False

Start debugging and show values of interest.

debugOn;
Dynamic @ Row[{clicks, on, debug`clicks, debug`on}, "  "]
0  42  0  True

Now here is the contrived example of code with an embedded debugging variable that can be turned on off. When debugging is on, the changes in the variable can observed dynamically.

DynamicModule[{colors = {Red, Blue}},
 Row[
  {Button["Change Color",
     If[debug`on, debug`clicks++]; colors = Reverse @ colors],
   Dynamic @ Graphics[{colors[[1]], Disk[]}]}, Spacer[10]]]

Evaluate the above code and click on the button five times. You would see the disk alternate between red and blue and the monitored variables would become

0  42  5  True

Note that the global variables are unaffected.

Now evaluate

debugOff

The row of monitored variables becomes

0  42  5  False

and clicking on the button still changes the color of the disk but no longer affects the monitored variables.

Also, and perhaps most important to the OP, there is no problem with associations.

debug`assoc = <|"a" -> 10|>;
debug`assoc["b"] = 20;
debug`assoc
<|"a" -> 10, "b" -> 20|>
$\endgroup$
  • $\begingroup$ This sounds reasonable. Will have to test in intended application, ie, the context is the whole project. But it is distributed through 30 notebooks that are evolving so referential transparency is an issue. In a reply to Albert's comment above, I summarize the intended use, to fit all project data, metadata, data stages, and queries and results in an association - but your approach may work just as well. $\endgroup$ – alancalvitti Feb 17 '18 at 22:08
  • $\begingroup$ @alancalvitti. I have never used this technique in a project as large as the one you describe. The largest project in which I used the technique had five development files -- A project control notebook, a main notebook, a user documentation notebook and two supporting packages. It worked well for me at that level; I hope it works for you with your larger project. $\endgroup$ – m_goldberg Feb 17 '18 at 23:02
  • $\begingroup$ @alancalvitti and m_goldberg, what do I need to read to link this answer with the question. I must say I'm confused. $\endgroup$ – Kuba Feb 17 '18 at 23:57
  • $\begingroup$ @Kuba. What I'm proposing here is that the OP can solve his underlying problem by using context qualified symbols. I tried to show that such symbols provide a simple and elegant way to do everything he wants to do with sub-values, including adding a new key-value pair to an association with Set (where sub-values failed). $\endgroup$ – m_goldberg Feb 18 '18 at 0:28
  • $\begingroup$ @Kuba. Discuss the matter in chat? $\endgroup$ – m_goldberg Feb 18 '18 at 0:28
4
$\begingroup$

The fundamental problem here is with the SubValues. They just don't play nicely with in-place modification.

As Albert Retey mentioned, you can just use Association but you don't seem thrilled by that, perhaps because you enjoy how myProject is inert. So here's a different way to get that inertness, branching off of something I showed here. We'll make a version of that simple object system for your projects:

BeginPackage["projObj`"];
projObj::usage = "a project wrapper";
BeginPackage["`Package`"];
setPart::usage = "sets a projObj part";
setKey::usage = "sets a projObj key";
EndPackage[];
Begin["Private`"];
projObj~SetAttributes~HoldFirst;
projObj[s_Symbol][k__] := s[k];
projObj /: projObj[s_Symbol][[k__]] := s[[k]];
setPart[projObj[s_Symbol], p__, v_] := s[[p]] = v;
setKey[projObj[s_Symbol], p__, v_] := s[p] = v;
mutate~SetAttributes~HoldAllComplete;
mutate[a_Symbol?(Head[#] === projObj &)[k__]~Set~v_] := 
  setKey[a, k, v];
mutate[a_Symbol?(Head[#] === projObj &)[[k__]]~Set~v_] := 
  setPart[a, k, v];
Language`SetMutationHandler[projObj, mutate];
projObj[a_Association] := 
  With[{u = Unique[project]}, u = a; projObj[u]];
End[];
EndPackage[];

Now you can do things like:

$project1 =
  <|
   "BaseDirectory" -> $BaseDirectory,
   "Deliverables" -> <||>
   |>;
proj1 = projObj[$project1]

projObj[$project1]

but treat proj1 just like an Association:

proj1["BaseDirectory"]

"/Library/Mathematica"

proj1["BaseDirectory"] = $TemporaryDirectory

"/private/var/folders/9t/tqc70b7d61v753jkdbjkvd640000gp/T"

proj1["Deliverables", "Puppies"] = <|"Quantity" -> "Many", 
  "Cuteness" -> "Very"|>

<|"Quantity" -> "Many", "Cuteness" -> "Very"|>

And the changes are mirrored on $project1:

$project1

<|"BaseDirectory" -> 
  "/private/var/folders/9t/tqc70b7d61v753jkdbjkvd640000gp/T", 
 "Deliverables" -> <|"Puppies" -> <|"Quantity" -> "Many", 
     "Cuteness" -> "Very"|>|>|>

And this is particularly useful as it lets you simply dump $project1 to a file to cache your state, e.g. via

Export[LocalObject["Projects/Project1"], $project1]

and reload that via

$project1=Get[LocalObject["Projects/Project1"]];
proj1=projObj[$project1]
$\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.