New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SCLang: ClassLib: Implement doesNotUnderstandAbout
preserving kwargs across doesNotUnderstand
calls
#6297
base: develop
Are you sure you want to change the base?
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
5994984
to
1a9c607
Compare
This comment was marked as resolved.
This comment was marked as resolved.
doesNotUnderstandWithKey
s preserving kwargs across doesNotUnderstand
callsdoesNotUnderstandWithKeys
preserving kwargs across doesNotUnderstand
calls
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
0e0606f
to
11d3108
Compare
This comment was marked as resolved.
This comment was marked as resolved.
11d3108
to
b18d8d2
Compare
When an object receives a message that it does not understand one of the following two methods are called before an error is thrown: link::#-doesNotUnderstand:: and link::#-doesNotUnderstandWithKeys::. | ||
This means it is possible to redirect the message, calling any other code desired. | ||
|
||
link::#-doesNotUnderstand:: is quasi-deprecated, meaning it is unnecessary to implement but maintained for compatibility reasons. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me, "deprecate" is too strong. Perhaps we should just describe it as a choice. Maybe I feel emotionally attached to the message doesNotUnderstand
!
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
OK, I think we have to live with two versions of doesNotUnderstand. I have two suggestions concerning naming.
What do you think from a native speaker's point of view? |
But I am still thinking about how we could just do it with |
I like the name
I don't think what Haskell does is important here, the clash with I don't think it will be ever be possible to, without breaking anything, extended Maintaining two methods that do almost but not quite the same thing seems like a recipe for trouble, and creating the possibility that the code might do something different depending on whether keyword arguments are passed or not also seems like it would cause issues down the line. For those two reasosn I do think that
I don't quite understand here because of how the a method call that isn't understood is resolved to what ever method the user has defined. A little ambiguity that I noticed just now... DNUWK_Base {
}
DNUWK_Child : DNUWK_Base {
doesNotUnderstandWithKeys { |selector, argumentsArray, keywordArgumentPairs|
^\child;
}
}
DNUWK_Child().foo(\abc, 1) // returns \child
DNUWK_Child().foo(\abc, car: 1) // returns \child This is all good. However, if DNUWK_Base {
doesNotUnderstand { |selector ...args|
^\base;
}
}
DNUWK_Child : DNUWK_Base {
doesNotUnderstandWithKeys { |selector, argumentsArray, keywordArgumentPairs|
^\child
}
}
DNUWK_Child().foo(\abc, 1) // returns \base
DNUWK_Child().foo(\abc, car: 1) // returns \child Here, implementing What do you think about making the default method be |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
b18d8d2
to
c159818
Compare
Ah, so I've just discovered something else when replacing This call to // old
^this.superPerformList(\doesNotUnderstand, selector, args);
// new — this goes into an infinite loop
^this.superPerformList(\doesNotUnderstandAbout, selector, argumentsArray, keywordArgumentPairs) To fix it, you have to do this... ^this.superPerformList(\doesNotUnderstandAbout, [selector, argumentsArray, keywordArgumentPairs]) This actually makes sense according to the help file, and (my understanding of) the c++, but I can't for the life of me figure out why it worked in the first place!? int objectSuperPerformList(struct VMGlobals* g, int numArgsPushed) {
...
// Here, if the argument isn't an object (an array of some kind) it just calls normal perform
// going into an infinite loop.
if (NotObj(listSlot)) {
return objectPerform(g, numArgsPushed);
}
...
// It is only in the last few lines where the actually super object gets call.
sendSuperMessage(g, selector, numArgsPushed);
g->numpop = 0;
return errNone;
} |
c159818
to
5c0d90a
Compare
When calling an unknown method without keyword args the supercollider vm passes all the information back into the language allowing for transparent wrapper classes. This is not the case with keyword arguments whose data is lost. This PR fixes that issue by implementing a new method in Object.sc `doesNotUnderstandAbout {|selector, argumentsArray, keywordArgumentsPairs|..}`. This commit contains all the core mechanisms of that change, and is broken into a couple of parts. By itself this commit is not that useful as it is hard to actually use the new method, the subsequent commits will address this. A huge thank you to @telephon for helping with the design of the class library methods and help documentation!
…long with error checking in Method/FunctionDef. These three methods are useful when using `doesNotUnderstandAbout` as they provide a way to call a function with both arguments and keyword arguments centralizing all the error checking inside of Method/FunctionDef. These `*with` method take can argumentArray and a keywordArgumentPairs array, check it against the Method or FunctionDef to ensure the keywords are valid and don't overlap with the non-keyword args.
Replaces the call to `doesNotUnderstand` in IdentityDictionary with `doesNotUnderstandAbout`, passing the keyword arguments on to the function.
…dAbout` Since `doesNotUnderstand` should no longer be used, this updates the uses in the class library.
5c0d90a
to
ae0347c
Compare
doesNotUnderstandWithKeys
preserving kwargs across doesNotUnderstand
callsdoesNotUnderstandAbout
preserving kwargs across doesNotUnderstand
calls
I've updated the pr to reflect these changes. I haven't changed the names of Hold off on looking at this though, I've just found a bug... |
Okay here is the current bug, and its not nice... T_Base {
doesNotUnderstand { |selector ...args|
^\base
}
}
T_Child : T_Base {
doesNotUnderstandAbout { |selector, argumentsArray, keywordArgumentPairs|
^\child
}
}
T_Child2 : T_Base {
doesNotUnderstandAbout { |selector, argumentsArray, keywordArgumentPairs|
this.postln;
^\child
}
}
T_Child().foo(1, 2) // returns \base
T_Child().foo(1, 2, bar: 3) // segfaults
T_Child2().foo(1, 2, bar: 3) // returns \child I've been staring at this for a few hours so will have to take a break. |
I have tried to find a way to have only one call back from the backend, but this doesn't work in the delegation logic as we would like it to. We need this to work: SubSubclass1().plink // -> \yes1
SubSubclass2().plink // -> \yes2 In Object {
doesNotUnderstand { arg selector ... args;
DoesNotUnderstandError(this, selector, args).throw;
}
doesNotUnderstandAbout { arg selector, args, keywordArgumentPairs;
DoesNotUnderstandError(this, selector, args, keywordArgumentPairs).throw;
}
}
Subclass : Object {
doesNotUnderstandAbout { arg selector, args, keywordArgumentPairs;
^\yes1
}
}
SubSubclass1 : Subclass {
doesNotUnderstand { arg selector ... args;
^super.doesNotUnderstand(selector, *args)
}
}
SubSubclass2 : Subclass {
doesNotUnderstand { arg selector ... args;
^\yes2
}
} |
Purpose and Motivation
When calling an unknown method without keyword args the supercollider vm passes all the information back into the language allowing for transparent wrapper classes. This is not the case with keyword arguments whose data is lost.
This PR fixes that issue by implementing a new method in Object.sc
doesNotUnderstandAbout {|selector, argumentsArray, keywordArgumentsPairs|..}
, creating methods to make using this easier, updating the objectprototyping mechanism, and updating the rest of the class libraryA huge thank you to @telephon who has been invaluable in designing the class library methods and the help documentation!
Fixes #6064.
See #6227 for the messy history!
This PR has four commits, 1 and 2 are functionality, 3 and 4 actually do something useful for the end user — the latter two also serve as an example of usage.
1 SCLang: ClassLib: implement doesNotUnderstandAbout.
This commit contains all the core mechanisms of
doesNotUnderstandAbout
. By itself this commit is not that useful as it is hard to actually use the new method.doesNotUnderstandAbout
.DoesNotUnderstandError
: add an additional argument, allowing the keyword pairs to be passed through.doesNotUnderstandWithKeys
to call the sclang methoddoesNotUnderstandAbout
by wrapping the non-keyword arguments and keyword arguments in separate arrays, if there are no keywords, passnil
.Exactly what method gets called when something isn't understood a a little complex to preserve backwards compatibility.
doesNotUnderstandAbout
method will be tested to see if it is the same asObjects
, if it isn't, thendoesNotUnderstandAbout
is called.doesNotUnderstand
, this is called, if there are keyword arguments in the call, a warning is posted.doesNotUnderstandAbout
is called.Object's
doesNotUnderstand
is never called by this mechanism. It is preserved and for the cases where the user manually calls the method. Object's implementation delegates todoesNotUnderstandAbout
.However... if a base class has implement
doesNotUnderstand
, it will called and the keywords discarded.Additionally, if a child of that base implements
doesNotUnderstandAbout
, then only when there are no keywords will the base class'sdoesNotUnderstand
method be called. This is confusing, but necessary for backwards compatibility.2 ClassLib: implement performWith, valueWith and functionPerformWith along with error checking in Method/FunctionDef.
Implement
performWith
,valueWith
, andfunctionPerformWith
which allow an easy way toperform
/value
/functionPerform
with both keyword arguments and normal arguments. This also centralises the error checking inMethod
/FunctionDef
so that you can't call a method with overlapping arguments and keyword-arguments.3 ClassLib: make objectPrototyping accept keywords.
Fix object prototyping so that keywords are passed to the function. Given the previous two commits, this is a small change.
4 ClassLib: replace calls to
doesNotUnderstand
withdoesNotUnderstandAbout
There are only a few places where
doesNotUnderstand
was used in the class library, this replaces the majority of them.Types of changes
To-do list