Reputation: 128447
I've run into this situation before, and something tells me the way I generally handle it is not the cleanest or most idiomatic.
Suppose I have a function that takes a block, which can in turn take 1 or 2 (say) parameters.
def with_arguments(&block)
case block.arity
when 1
block.call("foo")
when 2
block.call("foo", "bar")
end
end
with_arguments do |x|
puts "Here's the argument I was given: #{x}"
end
with_arguments do |x, y|
puts "Here are the arguments I was given: #{x}, #{y}"
end
Switching on arity
seems pretty hacky. Is there a more standard Ruby way to achieve this kind of thing?
Upvotes: 4
Views: 3501
Reputation: 1
def bar(&block)
puts 'In bar'
block.call(1) if block
puts 'Back in bar'
block.call(1,2) if block
end
1.9.3p392 :043 > bar do |*b| puts b.length end
In bar
1
Back in bar
2
Upvotes: 0
Reputation: 17030
Here's how I'd pass arbitrary arguments to a lambda
:
def with_arguments(&block)
args = %w(foo bar)
n = block.arity
block.call *(n < 0 ? args : args.take(n))
end
with_arguments &lambda { |foo| }
with_arguments &lambda { |foo, bar| }
with_arguments &lambda { |*args| }
with_arguments &lambda { |foo, *args| }
with_arguments &lambda { |foo, bar, *args| }
If n
is negative, then the lambda
takes an arbitrary number of arguments. Precisely (n + 1).abs
of these arguments are mandatory. One can use that information to decide which arguments to pass.
If the lambda
takes a finite number of arguments, then just pass the first n
elements of args
. If it takes an arbitrary number of arguments, then just pass the entire argument array.
The lambda
itself will handle the cases where args
is insufficient:
with_arguments &lambda { |foo, bar, baz, *args| }
# ArgumentError: wrong number of arguments (2 for 3)
You can simply pass the two arguments to the block:
def with_arguments(&block)
block.call 'foo', 'bar'
end
with_arguments { |x| puts x } # y is not used
with_arguments { |x, y| puts x, y } # All arguments are used
with_arguments { |x, y, z| puts x, y, z } # z will be nil
Unused block arguments are discarded, and any extra parameters will be set to nil
.
This is specific to regular blocks and Proc
s – lambda
s will raise an error if given the wrong number of parameters. You can actually find out whether this is the case by calling Proc#lambda?
Also, if you aren't going to store the block, it is cleaner to simply use yield
:
def with_arguments
yield 'foo', 'bar'
end
Upvotes: 7
Reputation: 146281
Some solutions...
Upvotes: 0