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
Implement doesNotUnderstandWithKeys
#6227
Implement doesNotUnderstandWithKeys
#6227
Conversation
I've added implementation for |
All documentation is now updated, and I have not been able to find any issues, this is now ready for a review. |
This is a very very useful addition, thanks. Currently, for me, the build is broken (due to a #6224), so I can't test it. But I am in favour of adding this feature. |
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.
In terms of naming and documentaton, this could be improved a little.
doesNotUnderstandWithKeysClassMethodHelper
- What does this method actually do?
- Maybe there is a simpler name?
withoutArray
argument: in the method call of doesNotUnderstandWithKeys
this is called withOutKeys
, isn't it?
And withEvent
is called withKeys
, right? This makes things harder to understand than necessary.
Also, I would write without
and not withOut
, because this is just one word.
This method will be very useful for object prototyping and calling pseudo-object code (envirs) with keyword arguments.
…Method and FunctionDef. Also add documentation and tests
Once you have the two types of arguments (without and with keywords) it is quite hard to do anything with them as there isn't any obvious way of combining them and calling a function. Perhaps keyword arguments were added later leading to an odd interface? In this recent commit I've added some more methods to FunctionDef to centralise the error checking, ensuring all keywords are accepted, and none of the arguments collide with each other. Having each class the uses There are then new versions of I admit that having so many ways to call a function is verbose, but this approach seems the most consistent with supercollider's code base. Here are some examples of how this all works together taken from the commit, which I think, provides a decent interface for anyone wanting to use this method. // From Scale, calls a class method
*doesNotUnderstandWithKeys {|selector, argsArray, keywordArgsAsPairs|
^Scale.class
.findRespondingMethodFor(\newFromKey)
.evaluateWithArgsAndKwArgs([selector] ++ argsArray, keywordArgsAsPairs.asEvent, Scale)
// last argument is 'this'
} // From IdentityDictionary
doesNotUnderstandWithKeys {|selector, argsArray, keywordArgsAsPairs|
if(know.not){
^this.superPerformList(\doesNotUnderstandWithKeys, selector, argsArray, keywordArgsAsPairs)
};
this[selector] !? {|f|
^f.functionPerformWithArgsAndKwArgs(\value, [this] ++ argsArray, keywordArgsAsPairs.asEvent)
};
this[\forward] !? {|f|
^f.functionPerformWithArgsAndKwArgs(\value, [this, selector] ++ argsArray, keywordArgsAsPairs.asEvent)
};
^nil
} There is also series of tests that pass. |
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.
great! Needs a few tweaks, but looks good already.
Maybe it helps to take a look at |
Thanks for your feedback @telephon, I've addressed the smaller issues in this last commit (and resolved the comments), the larger ones I will try and get to tomorrow. |
It would be easy to do one, this is how I would go about it. Ellipsis arguments would need to be taken care of still. (
f = { |func, args, keywordArgDict|
var callArgs = func.defaultArgs.copy.extend(func.argNames.size, nil);
callArgs.overWrite(args);
func.argNames.do { |name, i|
var val = keywordArgDict[name];
if(val.notNil) { callArgs[i] = val }
};
func.valueArray(callArgs)
}
)
f.({ |x, y = 0, freq, amp = 0.2| [x, y, freq, amp] }, [9], (freq:500, amp: 0.2))
|
I think I've address the points you have raised now. if(know.not){
^this.superPerformList(\doesNotUnderstandWithKeys, selector, argsArray, keywordArgsAsPairs )
};
this[selector] !? {|f|
^f.functionPerformList(
\value,
f.def.makePerformableArray([this] ++ argsArray, keywordArgsAsPairs.asEvent)
)
};
// Unlike in doesNotUnderstand, we don't convert to a setter as this isn't
// possible with keyword args.
// If the keyword arg names don't match this will fail.
// Note how the selector is passed in here.
this[\forward] !? {|f|
^f.functionPerformList(
\value,
f.def.makePerformableArray([this, selector] ++ argsArray, keywordArgsAsPairs.asEvent)
)
};
^nil |
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.
Here a few more suggestions. It is fun, I hope it is ok that there are so many requests. It is a deeper change, so we need to make it really as good as possible.
I've just pushed a commit that addressed your comments on the code, I think it look much better with your suggestions @telephon. I haven't finished looking at the docs yet, as I'll wait to see what happens with the other conversation before making progress there. |
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.
Many improvments thank you so much, I am sorry to have a few "backward" suggestions …
I just found another serious bug related to the way variable arguments are expanded while making For some testing function... f = { |a, b, c, d ...e|
var i = 0;
var j = 1;
a.debug(\a);
b.debug(\b);
c.debug(\c);
d.debug(\d);
e.debug(\e);
};
This works as expected. f.valueArray(\a, \b, \c, \d, [\e, \f, \g])
a: a
b: b
c: c
d: d
e: [e, f, g] This does not. a = [\a, \b, \c, \d, [\e, \f, \g]]
f.valueArray(a)
a: a
b: b
c: c
d: d
e: [[e, f, g]] <<<< double wrapped. valueArray is supposed to work by taking the last item on the stack and expanding it, which it does! a = [\a, \b, \c, \d, [\e, \f, \g]]
f.valueArray(*a) <<< teeny little star fixes problem
a: a
b: b
c: c
d: d
e: [e, f, g] <<<< correct ... making it quite brittle. This is quite a big change as many (but not all) use cases get this wrong, a quick grep shows about 80 calls to this method. This will also be the case for I will make a separate issue on this. [#6233] |
I don't think it is an issue, but see my comment there. |
Hm: https://scsynth.org/t/object-prototyping-is-not-broken-for-higher-oder-functions-but-why/9171 |
@JordanHendersonMusic should we discuss this now or shift it to a future "reform"? We can always add arguments to the methods Are there any open points we should discuss before we go through it once more for cosmetics? |
Is rather discuss that in the future as it's a larger change that isn't necessarily connected to this. I'd also need some time to play around with the idea a bit. I think everything is settled, there hadn't really been a review of the C++ but it is okay I think. |
Yes, I agree. So I'll go through everything and make copy editing level suggestions. |
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.
So this is a last copy edit, I just found one structural thing (we can drop the conversions with asDict
).
The keywordArgs argument names should still be unified across the code. Currently we have: keywordArgumentEnvir
, argsWithKeys
, and keywordArgsAsPairs
. Since no conversion needs to be done, these can now all be the same. E.g. keywordArgs
.
Maybe you can add a brief comment in the help files that anything that responds correctly to keysValuesDo
will do.
Keeping the docs all synchronised is harder than writing the code... I've renamed all the references to keywordArgumentEnvir to keywordArgumentPairs. |
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.
just final cleanup.
Would it be useful if I squashed the commits? Or is that something that gets done automatically? |
Ideally, there should be a third person looking over it and would then squash to commit. |
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.
I read through the whole PR (not yet into the depths of the codebase) so I only noted potential stylistic corrections. Apart from that, as far as I can see, this PR does what it claims to do.
This will be brilliantly useful, for error messages in keyword calls, |
So because this is actually quite a large change. I'm going to make a new PR with just a couple of small commits in to keep history clean and to make this easier to review. @adcxyz thanks, its something I've ran into in many places because it's needed to make proper wrapper classes! |
Purpose and Motivation
Information is needlessly lost when calling doesNotUnderstand when there are keywords args.
Now, when a selector isn't understood and there are keysword args in the call, the following method is called, here is Objects implementation.
Fixes #6064
Here is some behaviour as it relates to identity dictionary.
Old:
New.
I admit,
nil
is probably a better reason to meow.More examples
Also provides some helper methods in
Object
to make redirecting easier.These are used to call functions, class methods and methods respectively.
Types of changes
To-do list