Reputation: 84650
I'm trying to set up a method that takes a block as a parameter. I know you do this by giving the last parameter an & prefix, but once it's been passed, how should I verify it?
If I want to verify that an argument is a string, I could use is_a?(String)
, for example. But how do I verify that I have received a block that accepts one parameter? Or 2?
Upvotes: 5
Views: 2103
Reputation: 30021
You can use the Proc#arity
method to check how many arguments the block accepts:
def foo(&block)
puts block.arity
end
foo { } # => 0
foo { |a| } # => 1
foo { |a, b| } # => 2
From the documentation:
Returns the number of arguments that would not be ignored. If the block is declared to take no arguments, returns 0. If the block is known to take exactly n arguments, returns n. If the block has optional arguments, return -n-1, where n is the number of mandatory arguments. A proc with no argument declarations is the same a block declaring || as its arguments.
Upvotes: 12
Reputation: 369623
Blocks aren't objects, so you can't do anything useful with them (except yield
to them, of course).
I mean, there's no way to refer to them, they aren't even bound to a name:
def foo
yield 'foo'
end
foo do |bar| puts bar end
# foo
Inside foo
, the block isn't bound to any variable, you cannot even refer to it, so you obviously cannot query it about its parameters either.
You can however ask Ruby to convert the block to a Proc
and bind it to a parameter. Then, you can refer to it by name, and you have the full Proc
API at your disposal, including Proc#parameters
:
def foo(&blk)
blk.parameters
end
foo do |m1, m2, o1=:o1, o2=:o2, *splat, m3, m4,
ok1: :ok1, mk1:, mk2:, ok2: :ok2, **ksplat, &blk| end
# => [[:opt, :m1],
# [:opt, :m2],
# [:opt, :o1],
# [:opt, :o2],
# [:rest, :splat],
# [:opt, :m3],
# [:opt, :m4],
# [:keyreq, :mk1],
# [:keyreq, :mk2],
# [:key, :ok1],
# [:key, :ok2],
# [:keyrest, :ksplat],
# [:block, :blk]]
However, note that the idea of "arity of a block" is a hairy concept in Ruby, because of the loose parameter binding semantics of blocks. Parameter binding semantics of blocks are different than the parameter binding semantics of methods, in particular when it comes to arity:
Array
bound to the parameter (as if the parameter had been declared as a *splat
parameter).Array
and its individual elements are passed as arguments instead (as if they had been passed as a *splat
argument).nil
.All in all, the semantics are closer to that of assignments than method calls.
For example, you will note that even though m1
and m2
are declared as mandatory positional parameters in the block, Proc#parameters
lists their type as :opt
, i.e. optional parameters.
In other words: even a block that declares only one parameter still takes two arguments, and a block that declares two parameters can be invoked with just one argument.
One example where this is useful: the entire Enumerable
mixin is based on methods that yield
a single element. However, for Hash
, you really want to deal with two arguments, key, value
. And you can, because Hash#each
yield
s an Array
of two elements, and a block that declares two parameters but only receives one argument will "splat" that argument across its parameters, so that you end up with the key bound to key
and the value bound to value
without having to copy&paste two-argument versions of all of Enumerable
s methods.
Upvotes: 4