Background - I’m tinkering with code reuse patterns, I started with some OO stuff but found limitations and advice that the team saw functional-esque programming a more likely supported paradigm. Ruby seems OK for such although I must admit - I’m neither a Ruby programmer nor a seasoned functional programmer. Needless to say I’m crossing the line of supported syntax here and there …
The issue I’m having is that I have been using Procs and I call ‘parameters’ on the Procs (to do some parameters currying). i.e.
myproc = Proc.new do | a:, b:, c: |
do_stuff
end
f_that_raises = Proc.new do | method |
myproc.parameters
end
f_that_raises(my_proc)
This works fine when its in one buffer. As soon as I try to call this code in another buffer the Procs become Symbols and I get the following error
Error: [buffer 8, line 165] - NoMethodError
Thread death!
undefined method `parameters' for :myproc:Symbol
The same obviously happens if I use define (I’m kinda presumeing define is a shorthand/synonym for Proc anyway … ?).
Is there a correct way to do this? Or a workaround?
Or …can someone illuminate this area - perhaps there’s even documentation about how variables/symbols work across buffers etc that I haven’t come across yet.
To a point. I think Sonic Pi also uses procedural programming (it’s simple for a 10 year old child to understand) with a few functional programming hints thrown in here and there. That said, it seems to me that we’d be more likely to support (simple) functional programming concepts over Object Orientation.
Nope. define is a standard Ruby function that takes a block parameter. (This block being the definition of the function that you want to create in Sonic Pi). See here:
As far as the actual problem you’re having, I don’t know exactly, but I suspect it is a similar issue to what blipson was having with namespacing. The define method in turn asks the codebase to define your user method into a specially namespaced module. Defining a proc directly triggers no such mechanism, and as such they do not end up in the usual namespace.
As for a workaround: I can’t say quickly off the top of my head. I’d need to possibly know a little more about the exact system you’re trying to build, and/or play around with it myself a bit. Sonic Pi’s standard mechanisms for sharing state between buffers is (I think?) limited to the Time-State system, and using define for user functions.
Thanks for the reply - useful info. To be more precise you can reproduce the symptom using the minimal code below.
# buffer 1
define :play_sample do |xsample: :bd_haus, pos: 0, play_params: {}, **kw|
params = play_params.merge(kw)
sample xsample, **params
0
end
play_sample2 = Proc.new do |xsample: :bd_haus, pos: 0, play_params: {}, **kw|
params = play_params.merge(kw)
sample xsample, **params
0
end
play_sample.parameters # this call does not throw an exception
play_sample2.parameters # this call does not throw an exception
but
# buffer 2
play_sample.parameters # this call raises
play_sample2.parameters # this call also raises
The motivation is the following function that provides a ‘better’ currying functionality than the ruby built-in in my use case
kw_curry = Proc.new do | method |
-> (**kw_args) {
required = method.parameters.select { |type, _| type == :keyreq }
if required.all? { |_, name| kw_args.has_key?(name) }
method.call(**kw_args)
else
-> (**other_kw_args) { kw_curry(method)[**kw_args, **other_kw_args] }
end
}
end
I think you’ll find in the above examples that play_sample.parameters will not work anywhere, because you’re effectively trying to run 0.parameters - since 0 is the return value of play_sample
. (The play_sample2.parameters errors in buffer 2 for the same reason I mentioned earlier about namespacing).
You can still use introspection to return the list of parameters for play_sample in buffer 2 - you just need to use a different command: method(:play_sample).parameters. Is that helpful at all?