First, a precision about the dot notation. You can keep it as is for you current algorithm, but you might need this later on if you use IdentityDictionaries
(ID
) in some other ways.
dict[\key]
and dict.key
are not strictly equivalent.
When accessing a key
that references a value, they have the same result :
(
var dict = (
key: "value"
);
dict.key.postln;
dict[\key].postln;
)
But when using a key
that references a Function
, they behave differently.
dict.function
evaluates the function, passing the ID
as an implicit argument.
dict[\function].value()
evaluates the function without any implicit argument.
(
var dict = (
funcWithSelfAsArg: { |self, string|
self.postln;
string.postln;
},
funcWITHOUTSelfAsArg: { |string|
string.postln;
},
);
dict.funcWithSelfAsArg("hello");
"".postln;
dict[\funcWITHOUTSelfAsArg].value("world");
)
In some sense, the second syntax is almost always worse than the first.
But I think this allows to improve code readability, especially when you have ID
s that mix both values and functions.
In the ‘hack’ I proposed you, the trick is to first assign a Function
to a key
until this Function
is replaced with a value :
(
var dict = (
key: { |self| self[\key] = "value" };
);
// Syntax shows that key is a function for now
dict.key;
// Syntax shows that key is now a value
dict[\key].postln;
)
Let’s forget nesting for a moment, and discuss a special case that might concern you.
If you only want to store values inside the ID
. AND if ‘dynamic’ values only refers to ‘static’ values.
Then your ID
only stores three components :
- The init function.
- ‘static’ values
- Functions that allow to assign ‘dynamic’ values
Instead of evaluating functions ‘by hand’, we could just execute every function (except init) during initialisation :
(
var dict = (
static1: 5,
static2: 10,
dyn1: { |self| self[\dyn1] = self[\static1] * 5; },
dyn2: { |self| self[\dyn2] = self[\static2] * 3; },
init: { |self|
// Execute every function
self.keysValuesDo({ |key, value|
if(value.class === Function) {
if(key !== \init) {
self.perform(key);
};
};
});
// Then remove init
self.removeAt(\init);
// Return self instead of
// the result of previous line
self
},
).init;
dict.postln;
)
But if one of the function depends on the result of an other function, you need to do this by hand, because the keys of an ID
are unordered, so you cannot know which function will be executed first when keys are being iterated (nesting solves this however).
Now ‘inheritance’. You can compose ID
s so they inherit from the keys of other ID
s, using .proto()
or .parent()
, like in this example :
(
var human = (
eyeColor: "blue"
);
var jane = (
size: "6"
).parent_(human);
var john = (
size: "5"
).parent_(human);
// Jane is also an human,
// so she has an eye color
jane.eyeColor.postln;
// Let's change John's eye color
john.eyeColor = "green";
john.eyeColor.postln;
// This didn't affect Jane
jane.eyeColor.postln;
// Changing 'human' eyeColor
// only affects Jane because
// she still shares this property
// with the parent dict.
// It doesn't affect John
// because we reassigned it's key with
// john.eyeColor = "green";
// thus overriding the key
human.eyeColor = "red";
jane.eyeColor.postln;
john.eyeColor.postln;
)
First of all, this will allow you to ‘share’ the init function to several ID
s if needed :
(
// Parent containing shared functionalities
var dataDict = (
init: { |self|
self.keysValuesDo({ |key, value|
if(value.class === Function) {
if(key !== \init) {
self.perform(key); }; }; });
self.removeAt(\init);
self
},
);
// 'Child' containing specific datas
var myData1 = (
static1: 5,
static2: 10,
dyn1: { |self| self[\dyn1] = self[\static1] * 5; },
dyn2: { |self| self[\dyn2] = self[\static2] * 3; },
).parent_(dataDict).init;
var myData2 = (
static1: 8,
static2: 3,
dyn1: { |self| self[\dyn1] = self[\static1] * 0.5; },
dyn2: { |self| self[\dyn2] = self[\static2] + 6; },
).parent_(dataDict).init;
myData1.postln;
myData2.postln;
)
Your nesting problem comes from this line :
self.who = self.you * 2;
Here, self
only refers to them
, not to b
. And them
does not have a you
key.
But if b
was a parent of them
, it would have access to the you
key.
This gets a little technical here, but here’s the solution I can think of :
Starting from the top ID :
- First evaluate every function of the ID so values are set and accessible
- Recursively initialize every nested
ID
passing self as parent :
(
var dataDict = (
init: { |self|
var functions = List(0);
var nestedDicts = List(0);
// Reference functions and IDs
self.keysValuesDo({ |key, value|
case
{ value.class === Function } {
if(key !== \init) {
functions.add(key);
};
}
{ value.isKindOf(IdentityDictionary) } {
nestedDicts.add(key);
};
});
// Initialize functions
functions.do({ |key| self.perform(key); });
// Recursively initialize IDs,
// after setting self as their parent
nestedDicts.do({ |key|
self[key].parent_(self);
self[key].init;
});
// Now that we're done with this ID, clean up everything
functions.clear;
nestedDicts.clear;
self.removeAt(\init);
self
},
);
var myData = (
static1: 5,
static2: 10,
dyn1: { |self| self[\dyn1] = self[\static1] * 5; },
dyn2: { |self| self[\dyn2] = self[\static2] * 3; },
nestedDict: (
nestedValue: { |self| self[\nestedValue] = self[\dyn2] * 16; },
// Beware of symbol collisions
// since dicts inherit keys symbols from their parents,
// every key MUST have a unique name
nestedNestedDict: (
nestedNestedValue: { |self| self[\nestedNestedValue] = self[\nestedValue] * 4; },
),
),
).parent_(dataDict).init;
myData.nestedDict.nestedValue.postln;
myData.nestedDict.nestedNestedDict.nestedNestedValue.postln;
)