Ruby Block Taking Array or Multiple Parameters

Ruby block taking array or multiple parameters

Ruby's block mechanics have a quirk to them, that is if you're iterating over something that contains arrays you can expand them out into different variables:

[ %w[ a b ], %w[ c d ] ].each do |a, b|
puts 'a=%s b=%s' % [ a, b ]
end

This pattern is very useful when using Hash#each and you want to break out the key and value parts of the pair: each { |k,v| ... } is very common in Ruby code.

If your block takes more than one argument and the element being iterated is an array then it switches how the arguments are interpreted. You can always force-expand:

[ %w[ a b ], %w[ c d ] ].each do |(a, b)|
puts 'a=%s b=%s' % [ a, b ]
end

That's useful for cases where things are more complex:

[ %w[ a b ], %w[ c d ] ].each_with_index do |(a, b), i|
puts 'a=%s b=%s @ %d' % [ a, b, i ]
end

Since in this case it's iterating over an array and another element that's tacked on, so each item is actually a tuple of the form %w[ a b ], 0 internally, which will be converted to an array if your block only accepts one argument.

This is much the same principle you can use when defining variables:

a, b = %w[ a b ]
a
# => 'a'
b
# => 'b'

That actually assigns independent values to a and b. Contrast with:

a, b = [ %w[ a b ] ]
a
# => [ 'a', 'b' ]
b
# => nil

Block with two parameters

If you look at the documentation of Enumerable#find, you see that it accepts only one parameter to the block. The reason why you can send it two, is because Ruby conveniently lets you do this with blocks, based on it's "parallel assignment" structure:

[[1,2,3], [4,5,6]].each {|x,y,z| puts "#{x}#{y}#{z}"}
# 123
# 456

So basically, each yields an array element to the block, and because Ruby block syntax allows "expanding" array elements to their components by providing a list of arguments, it works.

You can find more tricks with block arguments here.

a.combination(2) results in an array of arrays, where each of the sub array consists of 2 elements. So:

a = [1,2,3,4]
a.combination(2)
# => [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

As a result, you are sending one array like [1,2] to find's block, and Ruby performs the parallel assignment to assign 1 to x and 2 to y.

Also see this SO question, which brings other powerful examples of parallel assignment, such as this statement:

a,(b,(c,d)) = [1,[2,[3,4]]]

How does Ruby Array #count handle multiple block arguments

Just like assignments, there's a (not-so-) secret shortcut. If the right-hand-side is an array and the left-hand-side has multiple variables, the array is splatted, so the following two lines are identical:

a, b, c = [1, 2, 3]
a, b, c = *[1, 2, 3]

While not the same thing, blocks have something in the same vein, when the yielded value is an array, and there are multiple parameters. Thus, these two blocks will act the same when you yield [1, 2, 3]:

do |a, b, c|
...
end

do |(a, b, c)|
...
end

So, in your case, the value gets deconstructed, as if you wrote this:

[[1,1], [2,2], [3,4]].count {|(a,b)| a != b} # => 1

If you had another value that you are passing along with the array, you would have to specify the structure explicitly, as the deconstruction of the array would not be automatic in the way we want:

[[1,1], [2,2], [3,4]].each.with_index.count {|e,i| i + 1 == e[1] }
# automatic deconstruction of [[1,1],0]:
# e=[1,1]; i=0

[[1,1], [2,2], [3,4]].each.with_index.count {|(a,b),i| i + 1 == b }
# automatic deconstruction of [[1,1],0], explicit deconstruction of [1,1]:
# a=1; b=1; i=0

[[1,1], [2,2], [3,4]].each.with_index.count {|a,b,i| i + 1 == b }
# automatic deconstruction of [[1,1],0]
# a=[1,1]; b=0; i=nil
# NOT what we want

Can I pass an array and a block as a parameter?

You can do it by passing the block outside the parentheses (adjacent to the method call):

p mapper([1, 2, 3, 4]) { |index| index * 2 }
# [2, 4, 6, 8]

Otherwise it'll result in a syntax error. Ruby won't know where the block is being passed.

As a side note, you can also define only the array as a needed argument, and then yield the block being passed:

def mapper(arr)
arr.size.times.map do |i|
yield(arr[i])
end
end

p mapper([1, 2, 3, 4]) { |index| index * 2 }
# [2, 4, 6, 8]

How does Ruby Array#map behave when the block has two parameters?

We are given two arrays:

RANKS = (("2".."10").to_a + %w(Jack Queen King Ace)).freeze
#=> ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
SUITS = %w(Hearts Clubs Diamonds Spades).freeze
#=> ["Hearts", "Clubs", "Diamonds", "Spades"]

Our first step is to compute the product of those two arrays:

arr = RANKS.product(SUITS)
#=> [["2", "Hearts"], ["2", "Clubs"], ["2", "Diamonds"], ["2", "Spades"],
# ["3", "Hearts"], ["3", "Clubs"], ["3", "Diamonds"], ["3", "Spades"],
# ...
# ["Ace", "Clubs"], ["Ace", "Diamonds"], ["Ace", "Spades"]]

To print the elements of the elements of this array we can write:

arr.map do |a|
rank = a[0]
suit = a[1]
p rank
p suit
end
"2"
"Hearts"
"2"
"Clubs"
"2"
...
"Ace"
"Spades"

As only one Ruby object is passed to a block at a time, having a single block variable makes perfect sense. The first element of arr is passed to the block and the block variable is assigned its value:

a = arr.first
#=> ["2", "Hearts"]

I then used the method Array#[] (actually, the first form of that method shown at the doc) to extract the first and second elements of the array a and assign those values to the variables rank and suit.

A second way we could do that is as follows.

arr.map do |a|
rank, suit = a
p rank
p suit
end

This uses Array#decomposition.

As a convenience, Ruby permits us to use array decomposition to compute multiple block variables directly:

arr.map do |rank, suit|
p rank
p suit
end

When passing the first element of arr to the block, Ruby executes the following:

rank, suit = arr.first
#=> ["2", "Hearts"]
rank
#=> "2"
suit
#=> "Hearts"

Sometimes the block variables are written in a more complex way. Suppose, for example,

arr = [[1, [2, [3, {:a=>4}]], 5, 6]], [7, [8, [9, {:b=>10}]], 11, 12]]] 

We might write the following:

arr.each do |a,(b,(c,d),e,f)|
p a
p b
p c
p d
p e
p f
end
1
2
3
{:a=>4}
5
6
7
8
9
{:b=>10}
11
12

This may seem somewhat advanced, but it is really quite straightforward. You will see what I mean if you compare the locations of the brackets contained in each element of arr with the locations of the parentheses surrounding groups of block variables.

Suppose now we had no interest in the values of b, e or f. We might then write:

arr.each do |a,(_,(c,d),*)|
p a
p c
p d
end
1
3
{:a=>4}
7
9
{:b=>10}

An important reason for writing the block variables like this is that it tells the reader which components of each element of arr are used in the block calculations.

Lastly, the following is typical of a pattern that one often encounters.

arr = [1, 3, 4, 7, 2, 6, 9]

arr.each_with_object([]).with_index { |(n,a),i| a << n*n if i.odd? }
#=> [9, 49, 36]

What we actually have here is the following.

enum0 = arr.each_with_object([])
#=> #<Enumerator: [1, 3, 4, 7, 2, 6, 9]:each_with_object([])>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator:
# [1, 3, 4, 7, 2, 6, 9]:each_with_object([])>:with_index>

The enumerator enum1 generates each value, passes it to the block, the block values are assigned values and the block calculation is performed. Here is what happens for the first three values generated by enum1.

(n,a),i = enum1.next
#=> [[1, []], 0]
n #=> 1
a #=> []
i #=> 0
a << n*n if i.odd?
#=> nil

(n,a),i = enum1.next
#=> [[3, []], 1]
n #=> 3
a #=> []
i #=> 1
a << n*n if i.odd?
#=> [9]

(n,a),i = enum1.next
#=> [[4, [9]], 2]
n #=> 4
a #=> [9]
i #=> 2
a << n*n if i.odd?
#=> nil

a #=> [9]

See Enumerable#each_with_object, Enumerator#with_index and Enumerator#next.

Passing multiple arguments to a block

sum is the accumulator object, for example the following code:

(1..10).inject(0) do |sum, num| #sum is initialized to 0 and passed in as the accumulator
sum + num #since sum is the accumulator, the result of this addition is stored in sum
end

The above code is the same as:

sum = 0
(1..10).each do
|num|
sum = sum + num
end

You can give the accumulator object any valid variable name, of course better with a meaningful name in the context.

Passing multiple code blocks as arguments in Ruby

You can't pass multiple blocks, per se, but you can pass multiple procs or lambdas:

Using 1.9 syntax:

opportunity ->{ @some_array.empty? }, ->{ @some_other_array.empty? }

and in the method itself:

def opportunity(lambda1, lambda2)
if lambda1.()
@opportunities += 1
end
if lambda2.()
@performances += 1
end
end

What's a clean way to allow a block to handle a variable number of arguments?

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 Procs – lambdas 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

Ruby - How to write a method that accepts a proc with varying numbers of params?

You can always find out how many arguments that Proc takes:

def map!(&block)
case block.arity
when 1
# Takes 1 argument
when 2
# Takes 2 arguments
else
# etc.
end
end

This is a common pattern if you need to handle different argument counts in a particular way.

It's worth noting that unless you need to shuffle up the order of the arguments passed in you can always pass in too many and the excess will be ignored:

def example(&block)
block.call(1,2,3)
end

example { |v| p v }
# => 1

Whats the ruby syntax for calling a method with multiple parameters and a block?

The space before parentheses is causing ruby to evaluate (:name, :text) as single argument before calling the method which results in a syntax error. Look at these examples for illustration:

puts 1      # equivalent to puts(1)       - valid
puts (1) # equivalent to puts((1)) - valid
puts (1..2) # equivalent to puts((1..2)) - valid
puts (1, 2) # equivalent to puts((1, 2)) - syntax error
puts(1, 2) # valid

Your way of providing the block is syntactically valid, however when the block is not in the same line as the method call it is usually better to use do ... end syntax.

So to answer your question you can use:

item(:name, :text) { label('Name') }

or:

item(:name, :text) do
label('Name')
end


Related Topics



Leave a reply



Submit