Identity Dictionary - Is this possible?

Hi All

I would like to do this out of convenience when building large identity dictionaries.

However, this throws an error. Is there someway to do this without having to declare and allocate the dictionary earlier?

Thanks,
Christian

a = (
	me: 600,
	you: a.me + 100
);

// I want it to print this
/*
a = (
	me: 600,
	you: 700
);
*/

Best way I can think of:

a = ().use {
    ~me = 600;
    ~you = ~me + 100;
};

The assignment operator is a bit misleading because it’s the first written but last executed – a bit weird convention dating back to BASIC. If it were written (me: 600, you: a.me + 100) ==> a then it would more accurately reflect the order of operations. This is why it isn’t possible to support your formulation as-is: a doesn’t get its value until after the whole parenthesized event is finished evaluating, hence the parenthesized event can’t refer to a variable that is as yet unassigned.

use stashes the event-in-progress as the current environment, so it is accessible while still evaluating.

hjh

This is one possible solution :

(
var a = (
	me: 600,
	
	you: { |self|
		self[\you] = self[\me] + 100;
	},
	
	init: { |self|
		self.you;
	}
).init;

a[\you]
)

You keep the ‘one block’ definition, but you lose clarity.

@jamshark70, you coded this fab Class LambdaEnvir a few years ago:

which allows one to write:

LambdaEnvir((
		a: 200,
		b: { ~a+2 }
)).b               //202

@jamshark70 @Dindoleon @semiquaver Thanks all for the excellent responses. The data structures of SC has been both fascinating and challenging me for a long time now. Aside from the several solutions to my current problem, the different approaches (and background info) has given much to think about.

Thanks greatly.
Christian

I’ve spent a bit of time going over the different responses to the question and thought yours would work best for me. For my purposes I’ve converted it dot notation as follows:

(

a = (
	me: 600,

	you: { |self|
		self.you = self.me + 100;
	},

	init: { |self|
		self.you;
	}
).init;

a.me.postln;
a.you.postln;

)

This is great. However, do you have any thoughts on how your answer might work with nested references? I’ve tried a few different things but can’t seem to get the references to work. Here is an example of what I mean in a basic form that obviously doesn’t work.

(

b = (
	me: 600,

	you: { |self|
		self.you = self.me + 100;
	},

	them: (
		who:  { |self|
			self.who = self.you * 2;
		},
	),

	init: { |self|
		self.you;
		self.them.who;
	}
).init;

b.me.postln;
b.you.postln;
b.them.postln;
b.them.who.postln;

)

Again, any help much appreciated.

Thanks,
Christian

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 IDs 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 IDs so they inherit from the keys of other IDs, 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 IDs 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;
)

Until you give it a name used by a method in any of the following classes…
[class Event, class Environment, class IdentityDictionary, class Dictionary, class Set, class Collection, class Object ]

e.g.

(
var dict = (
	numChannels: { |self, string|
		'This is never called with hello'.postln;
		self.postln;
		string.postln;
	}
);

dict[\numChannels].value("world"); 
// prints:
//    This never called with hello
//    world
//    nil

dict.numChannels("hello");   
// prints:
//   1
)
1 Like

should Event not throw a warning if you set a key to a method name? Seems easy enough…

The problem isn’t the key, it’s the dot accessing syntax. Using square brackets is fine and works as intended, except when it comes to the auto passed self argument. Which can easily be fixed by not doing what the thread asks and just assigning it to a var, then adding the keys, using the variable in the function.

I had this problem sometimes and I only use if I remember correctly the following:

a = (x: { |in| 3 + in }, y: 4)
a[\x].(10)

Is this difference explained in detail in the help documents? I found the following short example:
https://depts.washington.edu/dxscdoc/Help/Classes/Event.html#Event%20as%20a%20name%20space%20for%20keeping%20objects

Event definitely should not throw a warning for this, because object prototyping is not Event’s primary purpose.

Let’s not forget that object prototyping is a purpose for IdentityDictionary (not Event – know is defined for IdentityDictionary) that was glommed onto the main purpose: as a hash table. You don’t want to slow down every hash table everywhere in the system because the object might be used as a prototype.

One way to disambiguate the purpose is to have a dedicated class for object prototyping. That is, the idea that “Event is the right way to prototype in SC” isn’t really a good idea in the first place.

Would also be worth benchmarking respondsTo(key)) to see how much every put would be slowed down by adding a check for this.

hjh

I don’t know if this was the question :

a[\x].(10)

is equivalent to

a[\x].value(10)

To see the difference between dict[\function].() and dict.function :

(
{ 
	var dict = (function: {});
	dict[\function].value();
}.def.dumpByteCodes
)

(
{ 
	var dict = (function: {});
	dict.function;
}.def.dumpByteCodes
)

Object Prototyping is covered in the Environment helpfile.


But I tried to answer the initial question without mentioning this concept, because it’s more about organising datas rather than composing objects, even if I end up prototyping a bit to do so…

1 Like

While a lot of the name collisions go away if you make sure all the keys are_named_like_a_snake, technically, you’d need to check all those classes Event inherits from to ensure no collisions. This means checking the core and all installed quarks.

This gets even more annoying when you consider a seemingly innocuous member additional to one of these classes by either the dev team or a quark author could just break your code and you wouldn’t get an error.

Personally, I just don’t think it’s worth the hassle, so I don’t think it’s a good idea to use dot accessing on event.

So to answer the original question… no, make a factory function instead.

~mk_foo = {
   var foo = ();
   foo[\words] = \meow;
   foo[\say] = { 
      foo[\words].postln
   };

   foo
};

~fooA = ~mk_foo.();

This also means you don’t need the init function, which is nice :+1:

1 Like

Thanks!

With the following code

I wanted to say the following:

a = (x: { |in| 3 + in }, y: 4)
a[\x].(10) // works
a.x.(10) // does not work

FWIW – in my ddwProto quark, I did this:

p = Proto.new;

p.value = 10;

WARNING: 'value' is already a method for Proto. Conflicts may occur.

… because the object knows it’s going to be used as a prototype (it’s called Proto!), and using a setter method call to push in new data indicates some likelihood of using dot syntax to get the data out, then a warning is appropriate. (But I didn’t do the same for .put.)

For the OP:

a = Proto {
	~me = 600;
	~you = ~me + 100;
};

… and done, 99% of the confusion in this thread avoided.

~~

As a personal opinion, I would not have recommended Event-style object prototyping for this use case. Programming approaches could be imagined as “gaining points” for simplicity, reliability, readability, maintainability, speed, things like that, and “losing points” for unnecessary complexity, bugginess, unclear boundary conditions, illegibility etc. IMO Event-as-prototype stays on the plus side for simple, clear tasks. When the object modeling and relationships become more complex, then Event-as-prototype loses clarity. It becomes harder to differentiate objects from each other (because they’re all the same class); the repetitive use of self. within pseudomethods impairs readability (tolerable in a small and limited context, but for larger prototypes, it gets ridiculous); it’s too easy to make mistakes about self.; etc. (not to mention that I 100% disagree with Event/Environment about the handling of currentEnvironment scope for prototyping).

So I think Event-as-prototype should generally be used with caution. I have used it in a number of examples on the forum, but I guess as a general rule of thumb, I’d say, if the prototype definition is more than, oh, 30 lines, you probably should do it a different way.

hjh

2 Likes