Argument names in functions of IdentityDictionary (or children thereof) don't seem to work?

Why does this not work?

~e = ( \func: {|self, a| a + 5 } );

~e.func(5); //  8
~e.func(a: 5); // WARNING: keyword arg 'a' not found in call to IdentityDictionary:doesNotUnderstand

And, perhaps more annoyingly for my use case, I’d expect one of these (or both) to work, but they don’t.

~e = ( \func: {|self, a| a + 5 } );

~e.func.performWithEnvir(\value, 5); 
~e.performWithEnvir(\func, (a: 4));

Does anyone know why this doesn’t work as expected when normal functions do?

~f = {|a, b| a + b};
~f.(b: 4, a:1); // fine

I encountered the same thing recently.

e[\func].(a: 5) will work.

I also found that wrapping the function inside of another function makes your original code almost work, though you’ll need the dot between “func” and the arguments into it:

~e = ( \func:  { {|self, a| a + 5 } });

~e.func.(a: 5)

No dot returns the same warning that you received.

Strangely though ~e.class is an Event, not IdentityDictionary. So perhaps the difference is in the fact that the know arg defaults to true in Event, but false in the IdentityDictionary. …Nope, I just tried using an IdentityDictionary with know: true and no difference.

Perhaps see IdentityDictionary.doesNotUnderstand? I.e.

e = ( f: {|self, a| a + 1 } );
e.at('f').value(nil, 5) == 6;
e.at('f').value(a: 5) == 6;
e.doesNotUnderstand('f', 5) == 6;
e.f(5) == 6;

There doesn’t appear to be any documentation for IdentityDictionary.doesNotUnderstand? But even if it works, that’s an incredibly ugly way to call a function.

Perhaps see p.61 of the blue book (https://dl.acm.org/doi/pdf/10.5555/273)

If no matching method is found in any class in the superclass chain, the receiver is sent the message doesNotUnderstand:; the argument is the offending message.

And on p.95

respondsTo: aSymbol. Answer whether the method dictionary of the receiver’s class or one of its superclasses contains the argument, aSymbol, as a message selector.

e = ( f: {|self, a| a + 1 } );
e.respondsTo('f') == false;
e.f(5) == 6 // courtesy doesNotUnderstand

This is happening, and is also pretty odd:

x = { |self, a = 1| a + 5 }
x.class // -> Function
z = (\func: { |self, a = 1| a + 5 }) // -> ( 'func': a Function )
z.func.class // -> Integer

Seems like the Event is attempting to resolve the function. Maybe that’s why wrapping it in an extra set of curly braces works? (Though not in pseudo-method syntax).

z = (\func: {{ |self, a = 1| a + 5 }})
z.func.class // -> Function
z.func.(a: 5)

Okay I understand where the message is coming from and what it means… but still, why doesn’t e respond to f? I think you’ve just restated my problem, or have I misunderstood?

In that case, the self argument won’t work!

This would fix that, but now its really ugly!

~e = Environment.make({
	~c = 4;
	~f = { |self|  { |a|
			self.postln;
			a + self.c;
	} };
}).know_(true);

~e.f.(a:3) // 7

[…] why doesn’t e respond to f?

e is an instance of Event and Event doesn’t have an instance method called f?

e.f works by trapping doesNotUnderstand?

It’s a common idiom in Smalltalks, but it doesn’t interact particularly well with keyword arguments…

I only meant that, if it wasn’t entirely clear what path e.f was following you could look it up, if you liked, i.e.

https://github.com/supercollider/supercollider/blob/develop/SCClassLibrary/Common/Collections/Dictionary.sc#L537-L561

(Also, you can look up implementations easily from the editor, C-I in scide, C-c : in Emacs, &etc. It’s sometimes more helpful than the help!)

I had the same question few times ago. I’ve messed a bit with it but didn’t really understand what was going on. The double function enclosure does the trick anyway.

I ended up finding that both Environment and IdentityDictionary do not respond to .addUniqueMethod, which I though might be related to this problem, but it also might not.

Ahh! Thank you, now I see.

doesNotUnderstand { arg selector ... args;.... has no keyword ‘f’. So args is an empty array in the function body… so nothing is passed through at all, and this is because the following line doesn’t attempt to pass the arguments on somehow, and this can’t be altered in SCLang because of this one line in c++.

It would be nice to be able to defer the keywords as an Environment until the instance method has been found and then call performWithEnvir on it. I’d have a go at altering the c++, but its particularly nasty, in fact, it doesn’t look like anyone has modified this function seriously since James McCartney’s Initial revision commit in 2002!!

Anyway, thanks for pointing to the code, I am really unfamiliar with smalltalk (kinda hate it) particularly the terminology, but reading the code is always easier!