(Crossposting note: I have asked this already at the Ruby Forum one week ago, but did not get any response yet).
Here is a (very) simplified, working version of what I have so far:
# A class S with two methods, one which requires one parameter, and
# one without parameters.
class S
def initialize(s); @ms = s; end
def s_method1(i); puts "s_method1 #{i} #{@ms}"; end
def s_method2; puts "s_method2 #{@ms}"; end
end
# A class T which uses S, and "associates" itself to
# one of the both methods in S, depending on how it is
# initialized.
class T
def initialize(s, choice=nil)
@s = S.new(s)
# If choice is true, associate to the one-parameter-method, otherwise
# to the parameterless method.
@pobj = choice ? lambda { @s.s_method1(choice) } : @s.method(:s_method2)
end
# Here is how I use this association
def invoke
@pobj.call
end
end
In this example, depending on how T is constructed, T#invoke calls either S#s_method1 or S#S_method2, but in the case of calling S#s_method1, the parameter to s_method1 is already fixed at creation time of the T object. Hence, the following two lines,
T.new('no arguments').invoke
T.new('one argument', 12345).invoke
produce the output
s_method2 no arguments
s_method1 12345 one argument
which is exactly what I need.
Now to my question:
In the case, where choice is nil, i.e. where I want to invoke the
parameterless method s_method2, I can get my callable object in an
elegant way by
@s.method(:s_method2)
In the case where choice is non-nil, I had to construct a Proc object
using `lambda. This not only looks clumsy, but also makes me feel a bit
uncomfortable. We have a closure here, which is connected to the
environment inside the initialize method, and I'm not sure whether this
could cause trouble by causing memory leaks in some circumstances.
Is there an easy way to simply bind a method object (in this case
@s.method(:s_method1) to a fixed argument?
My first idea was to use
@s.method(:s_method1).curry[choice]
but this does not achieve my goal. It would not return a callable Proc object, but instead actually execute s_method1 (this is not a bug, but documented behaviour).
Any other ideas of how my goal could be achieved?
This option is simple, but it might not be what you're looking for :
class T
def initialize(s, choice=nil)
s = S.new(s)
@choice = choice
@pobj = s.method(choice ? :s_method1 : :s_method2)
end
def invoke
@pobj.call(*@choice)
end
end
T.new('no arguments').invoke
T.new('one argument', 12345).invoke
#=> s_method2 no arguments
#=> s_method1 12345 one argument
# Allows setting default parameters for methods, after they have been defined.
module BindParameters
refine Method do
def default_parameters=(params)
@default_params = params
end
def default_parameters
@default_params || []
end
alias_method :orig_call, :call
def call(*params)
merged_params = params + (default_parameters[params.size..-1] || [])
orig_call(*merged_params)
end
end
end
Here's an example :
def f(string)
puts "Hello #{string}"
end
def g(a, b)
puts "#{a} #{b}"
end
using BindParameters
f_method = method(:f)
f_method.default_parameters = %w(World)
f_method.call('user') # => Hello user
f_method.call # => Hello World
g_method = method(:g)
g_method.default_parameters = %w(Hello World)
g_method.call # => Hello World
g_method.call('Goodbye') # => Goodbye World
g_method.call('Goodbye', 'User') # => Goodbye User
Your code can be rewritten :
class T
using BindParameters
def initialize(s, *choice)
s = S.new(s)
@pobj = s.method(choice.empty? ? :s_method2 : :s_method1)
@pobj.default_parameters = choice
end
def invoke
@pobj.call
end
end
T.new('no arguments').invoke # => s_method2 no arguments
T.new('one argument', 12_345).invoke # => s_method1 12345 one argument
If it is acceptable to patch the Method class, you could use :
class Method
def default_parameters=(params)
@default_params = params
end
def default_parameters
@default_params || []
end
alias_method :orig_call, :call
def call(*params)
merged_params = params + (default_parameters[params.size..-1] || [])
orig_call(*merged_params)
end
end
T becomes :
class T
def initialize(s, *choice)
s = S.new(s)
@pobj = s.method(choice.empty? ? :s_method2 : :s_method1)
@pobj.default_parameters = choice
end
def invoke
@pobj.call
end
end
This way is probably cleaner if you don't want to pollute Method class :
class MethodWithDefaultParameters
attr_accessor :default_parameters
attr_reader :method
def initialize(receiver, method_symbol)
@method = receiver.public_send(:method, method_symbol)
@default_parameters = []
end
def call(*params)
merged_params = params + (default_parameters[params.size..-1] || [])
method.call(*merged_params)
end
def method_missing(sym, *args)
method.send(sym, *args)
end
end
T becomes :
class T
def initialize(s, *choice)
s = S.new(s)
@pobj = MethodWithDefaultParameters.new(s, choice.empty? ? :s_method2 : :s_method1)
@pobj.default_parameters = choice
end
def invoke
@pobj.call
end
end
Any comment or suggestion are welcome!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With