Custom Class as a key in an identity dictionary isn't working?

IdentityDictionary (and its subclasses) are mainly used with Symbol keys, but of course you can use other types.

But yeah, it is indeed a bit strange that Event.asDict returns a new IdentityDictionary, even though an Event already is an IdentityDictionary.

Compare this with:

f = FloatArray();
g = f.asArray; // FloatArray[  ]
g === f; // true

I thought that was what overriding identityHash and === would do?

This isn’t allowed in Smalltalks?

Squeak comments copied below (note: Smalltalk spells identity and equality as == and =, not === and ==)

ProtoObject>>== anObject

	"Primitive. Answer whether the receiver and the argument
	are the same object (have the same object pointer). Do
	not redefine the message == in any other class!
	Essential. No Lookup. Do not override in any subclass.
	See Object documentation whatIsAPrimitive."

	<primitive: 110>
	self primitiveFailed

ProtoObject>>identityHash

	"Answer a SmallInteger whose value is related to the
	 receiver's identity.  Behavior implements identityHash
	 to allow the VM to use an object representation which
	 does not include a direct reference to an object's class
	 in an object.  If the VM is using this implementation
	 then classes are held in a class table and instances
	 contain the index of their class in the table.  A
	 class's class table index is its identityHash so that an
	 instance can be created without searching the table for
	 a class's index.  The VM uses this primitive to enter
	 the class into the class table, assigning its
	 identityHash with an as yet unused class table index. If
	 this primitive fails it means that the class table is
	 full.  In Spur as of 2014 there are 22 bits of
	 classTable index and 22 bits of identityHash per object.

	 Primitive. Essential. Do not override. See Object
	 documentation whatIsAPrimitive."

	<primitive: 175>
	self primitiveFailed

Object>>= anObject

	"Answer whether the receiver and the argument represent
	the same object. If = is redefined in any subclass,
	consider also redefining the message hash."

	^self == anObject

Object>>hash

	"Answer a SmallInteger whose value is related to the
	receiver's identity.  May be overridden, and should be
	overridden in any classes that define = "

	^self scaledIdentityHash
1 Like

1/

(
f = { |class, level = 0|
	String.fill(level, $\t).post;
	class.postln;
	class.subclasses.do { |subcl|
		f.(subcl, level + 1)
	};
};

f.(Dictionary);
""
)

Dictionary
	IdentityDictionary
		Environment
			Event
		NodeMap
			ProxyNodeMap

Event and IdentityDictionary are not siblings; Event is a grandchild. Agreed that it seems a bit arbitrary, but it isn’t flat out illogical.

2/ Help says: “Answer a corresponding dictionary. This is part of the Key Value Pairs interface” and Key Value Pairs help says:

asPairs returns an array of key value pairs
asAssociations returns an array of associations
asDict returns an IdentityDictionary.

… which doesn’t explain why (and omits that you can choose the result class), but at least something is mentioned.

3/ Use the source, Luke.

Dictionary:

	asDict { arg mergeFunc, class;
		// the mergeFunc is ignored, because dictionary keys must differ
		class = class ? IdentityDictionary;
		^if(class.notNil and: { class == this.class }) { this } { this.as(class) }
	}

(Note also, this means that if you pass in the same class as the original dictionary, you get the receiver, not even a copy.)

Collection:

	asDict { |mergeFunc, class|
		var res = (class ? IdentityDictionary).new;
		... snip
	}

But it’s actually that argument mergeFunc that gives me the biggest clue. So far the discussion of asDict has assumed that you’re starting with a dictionary, but if you’re starting with an array of pairs, then there is no guarantee of unique keys. So it isn’t a simple as as(Dictionary) or collectAs.

// default merge behavior is to overwrite
[0, 1, 0, 2, 0, 3, 1, 2, 1, 4].asDict;
-> IdentityDictionary[ (1 -> 4), (0 -> 3) ]

// but you could collect all of them, for instance
[0, 1, 0, 2, 0, 3, 1, 2, 1, 4].asDict({ |new, old| old.asArray.add(new) });
-> IdentityDictionary[ (1 -> [ 2, 4 ]), (0 -> [ 1, 2, 3 ]) ]

So asDict is really meant for the case where you have something (not necessarily a dictionary) that can be understood in terms of key-value pairs, and you want to convert it to a specific class of Dictionary (which is, arbitrarily, IdentityDictionary by default but AFAICS you should really specify what you want here).

So this looks like a case of seeing a method asDict and making assumptions about what it “should” do, and those assumptions turned out not to be correct.

Back to the top, then…

Something else that is related and a little annoying is when you try to convert from an Event to a Dictionary, it seems like .asDict would be the correct call…

myEvent.asDict(class: Dictionary)

// or use the general collection-conversion method 'as'
myEvent.as(Dictionary)

Of that word “annoying,” apologies but I’m going to editorialize (this has been coming for awhile). I’m noticing in the last six months to a year or two that it’s becoming more and more common to label quirks or inconsistencies as annoyances. There are legitimate deficiencies in sclang (e.g. Nathan’s reference to error handling and debugging) but I’m starting to get the feeling that this is creating a mental frame in which sclang is seen as a fundamentally deficient programming environment. Within that mental frame, then, it’s natural to react with irritation to every idiosyncratic decision, no matter how small. (asDict… gosh, did I ever use it in 20 years? Pretty sure I didn’t, not even once. It’s odd, but not annoying. Event, I do use daily, but does it really deserve the “rage-filled diatribe” Nathan spoke of in his blog post?)

Based on the time frame, I wonder if some of it has to do with the pandemic: more time inside at the computer, less sense of perspective. I know my sense of perspective got damaged during the last couple of years.

Let’s please not blame James McCartney for failing to be perfect. More and more often I’m catching a whiff here of “sheesh, why would anyone design it that way? I would do it better – and who wrote that horrible documentation?” but the thing is, at that time, nobody thought of merging object-oriented programming and an audio engine, but he actually did it and made it work. And, like all successful open source projects, it has a point of view, with which you’re not going to agree all the time.

So I see a couple of ways forward. One is to focus more attention on what sclang does right. I’m going to take SC on stage in a few days and run it for up to 2 hours, creating and destroying tens of thousands of nodes, and (knock on wood) it isn’t going to crash. This is amazing. Really. It’s astonishing. It’s why I stayed with it for 20 years.

Or, if one can’t shake the sense of deficiency, then there are a couple of choices: 1/ put energy into a less deficient SC4 (“the best piece of music criticism is to write better music” and the same goes for software), or 2/ switch to a different language that will bother you less.

hjh

1 Like

creating a mental frame in which sclang is seen as a fundamentally deficient programming environment

I think there are two kinds of Supercollider users:

  1. for whom sclang is their first (and only) programming language
  2. who have used other programming languages/environments

I have the feeling people in camp nr. 1 are more willing to put up with sclangs idiosyncasies. Once you get to know more and more programming languages/environments, you start to get more critical of all of them :smiley:

But then again, nothing is perfect.

“If you don’t like something, change it. If you can’t change it, change your attitude.”

I’m a sub set of this. I want to use a programming language to make art because it lets me wrap up all the smaller details into types which are important to my practice and manipulate them. An algebra of artistic concepts if you will. I don’t use supercollider because its supercollider, I use it because it is the only real option out there.

There simply isn’t one, supercollider is the best tool available right now.

This reminds me of a Bjarne Stroustrup quote:

“There are only two kinds of languages: the ones people complain about and the ones nobody uses”

But if a language was to be made today, what would it look like?

Well there would be no language barrier between ugen code and client code. All the audio code would be written in the language itself, JIT compiled for the specific sample rate and block size, Julia has proven you can produce code fast enough this way (actually, this might be faster than the current solution as you can perform more loop unrolling and vectorisation). This would get rid of the division between signal processing code for engineers and artists. This has huge pedagogical implications.

It probably wouldn’t be GPL. This would allow for the language to be embedded in things like game engines, creating an industry for students - something that is lacking from the current state of technology, despite the fact something like supercollider is an excellent tool to make interactive music with. If there is a JIT backend, then the whole code could be amazingly fast.

There would be a optional type system with multiple dispatch, removing the large hierarchy issues we have today. There would also be some for of trait/concept kind, where any object type could be selected and if it didn’t implement the interface, a nice error would be produced telling you how to fix it. Rust is excellent in that regard.

… I could go on but shan’t.

The point is there is a lot that could be better. Most of the tech I mentioned above didn’t exist in the 90 or even 10 years ago. I don’t think anyone is criticising from a position of spite here, particularly when discussing advice for beginner - you can’t give advice to beginners if you think no one should learn the language in the first place.

I guess the question I have about this is, can the SC community pool its collective resources and skills and make it happen, or do we have to wait for the next JMc (that is, one person with that unique combination of language design and audio skills, a forward-looking vision, and the free time to build it)?

I certainly don’t have the skills or the time, but I’m also getting along quite well with SC.

(Incidentally an earlier version of SC3.5 did translate UGen graphs into C, to compile entire SynthDefs directly to object code – but compiling was too slow in 2000, so he abandoned that approach.)

In any case, actually I agree with you that SC needs to move out of the late 90s / early aughts, or it will eventually be out of date. (See for example the slow rate of posting on the Pure Data forum… that platform appears not to be attracting new users at a rate that would sustain it into the future.) But it comes down to, who and how.

hjh

Use Dictionary @Spacechild1 .

Can’t inherit from symbol as its a primitive, other languages allow you to define a custom hash function to get around this, but SC does not as it would require overriding identity which is too much power for the user @scztt - other smalltalks disallow this @rdd.

Alternatively subclass IdentityDictionary and place validation on the put method @scztt.


Summarised so I can mark a solution in case anyone else find this.

1 Like

@lnihlen has been working on a reimplementation of sclang if I understand it correctly…

I was, but got tired of working on it alone and in the face of such serious cultural headwinds. Hadron, and Scintillator, my other SC-focused dev project to build a video synth, are both on indefinite hiatus, likely permanent.

Some tiny remarks in haste, apologies…

…in the last six months to a year or two that it’s becoming more and more common…

I’m pretty sure there’ve been people complaining, sometimes bitterly, about various aspects of Sc for longer than that, even back in the olden days of sc-users!

…at that time, nobody thought of merging object-oriented programming and an audio engine…

Scaletti, Carla “Kyma: an object-oriented language for music composition” Proc. ICMC, 1987 &etc.

It was a big topic! c.f. “The Well Tempered Object” (Pope 1991) &etc.

…supercollider […] is the only real option out there…

Except for Kyma and Faust and Gamma &etc &etc!

Also, scsynth has a tiny binary protocol, it’s very easy to talk to. Perhaps ScalaCollider would be more to your taste?

…no language barrier between ugen code and client code…

There has to be some kind of “barrier” because synthesis programs (that run in real-time) have all sorts of contraints people generally don’t want have apply to all of the other programs they write.

This is the problem “The Art of the Metaobject Protocol” (Kiczales et. al, 1991) is devoted to (c.f. Kiczales’ “Why are black boxes so hard to reuse?”)

William Cook had a nice project called “Enso” that provided “schemas” and (bi-directional) “grammars” in place of a “language”, but it’s a hard problem…

And the confusion one sees over and again on the Sc lists about where the “barriers” presently are kind of suggests they could be made clearer, if anything?

…most of the […] above didn’t exist in the 90s…

Are you sure? Traits, multiple dispatch, optional typing, partial evaluation, online compiling/decompiling &etc. &etc. all have rather long histories…

For instance, Kyma has been doing automatic signal graph partitioning for multiple processors for 30 years or so! “The Software Architecture of the Kyma System” (Hebel & Scaletti, Proc. ICMC, 1993) is a nice overview

https://quod.lib.umich.edu/i/icmc/bbp2372.1993.033/1

3 Likes