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 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
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
How to Add Iedriverserver to Path
What Does "File.Sync = True" Do
Omission of Curly Braces for a Hash in an Array
Jekyll Plugin Not Work on Github
Ruby on Rails: Fully Functional Tableless Model
Secure Erasing of Password from Memory in Ruby
Need Advice: Structure of Rails Views for Submenus
Fresh Rails App Defaulting to Postgres Instead of SQLite3
How to Display a Regexp Output in an Alphabetical List
Ruby: Looking for Ruby-Embeddable Interpreter or Scripting Language
Ruby - Convert Integer to String
How to Spec Methods That Exit or Abort
Ruby: Wait for All Threads Completed Using Join and Threadswait.All_Waits - What the Difference
How to Http Post Stream Data from Memory in Ruby
How to Make Empty Tags Self-Closing with Nokogiri
Sorting Numeric Strings in Ruby
Pass Arguments by Reference to a Block with the Splat Operator