Jackson
Jackson

Reputation: 405

Ruby passing arbitrary function and parameters into another function

I'm trying to learn Ruby. I want to pass an arbitrary function and an arbitrary list of arguments and keyword arguments into another function.

for example, I have this arbitrary function below

def dummy_func(a, b)
   return a+b
end

And I have this wrapper function

def wrapper(func, *args, **kwargs)
    func(args, kwargs))
end

I want it so I can pass my arguments in any of the following ways and still return the correct answer

wrapper(dummy_func, a=1, b=2)
wrapper(dummy_func, 1, b=2)
wrapper(dummy_func, a=1, b=2)
wrapper(dummy_func, 1, 2)

Is this possible in Ruby? What would be an idiomatic way of approaching it?

Upvotes: 0

Views: 1131

Answers (2)

Schwern
Schwern

Reputation: 164639

The idiomatic way is to instead yield to a block.

def dummy_func(a, b, key:)
   return a+b+key
end

def wrapper
  puts yield
end

a = 4
b = 5
c = 6
wrapper do
  dummy_func(a ,b, key: c)
end

Since the block is closure it can see all the same variables that the call to wrapper can. Now there's no need to pass wrapper's arguments through.


If you really want to make your wrapper, you can do some introspection to determine what arguments the wrapped function takes.

def dummy_func(a, b=23, key: 42)
   return a+b+key
end

def no_keys(a, b=23)
   return a+b
end

def wrapper(func, *array, **hash)
  method = self.method(func)
  takes_array = method.parameters.any? { |p| [:req, :opt, :rest].include?(p[0]) }
  takes_hash = method.parameters.any? { |p| [:keyreq, :key, :keyrest].include?(p[0]) }
  
  if takes_array && takes_hash
    self.send(func, *array, **hash)
  elsif takes_array
    self.send(func, *array)
  elsif takes_hash
    self.send(func, **hash)
  else
    self.send(func)
  end
end

a = 4
b = 5
c = 6
puts wrapper(:dummy_func, a, b, key:c)
puts wrapper(:no_keys, a, b)

But this is quite a bit more complex and less flexible than yielding to a block. This also limits you to "functions" which are really methods on the main object (there are no function references in Ruby). That's why they're called with self.send. Blocks require no assumptions about what is being wrapped.

Upvotes: 2

Mark
Mark

Reputation: 6445

The closest you can get is keyword arguments:

https://www.justinweiss.com/articles/fun-with-keyword-arguments/

def hello_message(greeting, time_of_day, first_name:, last_name:)
  "#{greeting} #{time_of_day}, #{first_name} #{last_name}!"
end

args = ["Morning"]
keyword_args = {last_name: "Weiss"}

hello_message("Good", *args, first_name: "Justin", **keyword_args)
=> "Good Morning, Justin Weiss!"

Upvotes: 0

Related Questions