What's this &block in Ruby? And how does it get passed in a method here?
Blocks are a fairly basic part of ruby. They're delimited by either do |arg0,arg1| ... end
or { |arg0,arg1,arg2| ... }
.
They allow you to specify a callback to pass to a method.
This callback can be invoked two ways - either by capturing
it by specifying a final argument prefixed with &
, or by
using the yield
keyword:
irb> def meth_captures(arg, &block)
block.call( arg, 0 ) + block.call( arg.reverse , 1 )
end
#=> nil
irb> meth_captures('pony') do |word, num|
puts "in callback! word = #{word.inspect}, num = #{num.inspect}"
word + num.to_s
end
in callback! word = "pony" num = 0
in callback! word = "ynop" num = 1
#=> "pony0ynop1"
irb> def meth_yields(arg)
yield(arg, 0) + yield(arg.upcase, 1)
end
#=> nil
irb> meth_yields('frog') do |word, num|
puts "in callback! word = #{word.inspect}, num = #{num.inspect}"
word + num.to_s
end
in callback! word = "frog", num = 0
in callback! word = "FROG", num = 1
#=> "frog0FROG1"
Note that our callback was the same in each case - we can remove
repetition by saving our callback in an object, and then passing it to each
method. This can be done using lambda
to capture the callback in an object,
and then passed to a method by prefixing it with &
.
irb> callback = lambda do |word, num|
puts "in callback! word = #{word.inspect}, num = #{num.inspect}"
word + num.to_s
end
#=> #<Proc:0x0052e3d8@(irb):22>
irb> meth_captures('unicorn', &callback)
in callback! word = "unicorn", num = 0
in callback! word = "nrocinu", num = 1
#=> "unicorn0nrocinu1"
irb> meth_yields('plate', &callback)
in callback! word = "plate", num = 0
in callback! word = "PLATE", num = 1
#=> "plate0PLATE1"
It's important to understand the different uses of &
here as a prefix to the last argument of a function
- in a function definition, it captures any passed block into that object
- in a function call, it expands the given callback object into a block
If you look around blocks are used all over the place, especially in iterators, like Array#each
.
In ruby, what does &block do?
Blocks
give you an opportunity to state a callback to pass on to a method.
The &
is key here - like @pst mentioned, it "promotes" the block to a Proc and binds the Proc to the variable with the given name.
With &
def time(&block)
puts block
end
time
# => nil
time { foo }
# => #<Proc:0x00029bbc>
Without &
def time(block)
puts block
end
time { foo }
# => ArgumentError: wrong number of arguments (0 for 1)
# Because & isn't included, the method instead expected an arguement,
# but as a block isn't a arguement an error is returned.
Difference between block and &block in Ruby
block
is just a local variable, &block
is a reference to the block passed to the method.
def foo(block = nil)
p block
end
foo # => nil
foo("test") # => test
foo { puts "this block will not be called" } # => nil
def foo(&block)
p block
end
foo # => nil
foo("test") # => ArgumentError: wrong number of arguments (1 for 0)
foo { puts "This block won't get called, but you'll se it referenced as a proc." }
# => #<Proc:0x0000000100124ea8@untitled:20>
You can also use &block
when calling methods to pass a proc as a block to a method, so that you can use procs just as you use blocks.
my_proc = proc {|i| i.upcase }
p ["foo", "bar", "baz"].map(&my_proc)
# => ["FOO", "BAR", "BAZ"]
p ["foo", "bar", "baz"].map(my_proc)
# => ArgumentError: wrong number of arguments (1 for 0)
The variable name block
doesn't mean anything special. You can use &strawberries
if you like, the ampersand is the key here.
You might find this article helpful.
How to use &block in Ruby
After think and searched in google I found the best way to do this, and understood how to use &blocks
and yields
in Ruby.
In the bigining, my problem was this repeted code:
def horizontally_position
res = []
for x in position[0]..end_coord(0)
res << [x, position[1]]
end
return res
end
def vertically_position
res = []
for y in position[1]..end_coord(1)
res << [position[0], y]
end
return res
end
The first thing I do to aboid the repeated code was use a
yield
:
def position_by(coord) # ²
res = []
for n in position[coord]..end_coord(coord)
res << yield(n) # ³
end
return res
end
def horizontally_position # ¹
position_by(0) { |n| [n, position[1]] } # ⁴
end
def vertically_position # ¹
position_by(1) { |n| [position[0], n] } # ⁴
end
¹ When this method will called, it will call to position_by(1)
or position_by(0)
.
² This method will start to execute and when it comes to << yield(n)
...
³ It will come again to vertically_position
" or horizontally_position
and will replace the yield
in position_by
for the code in brackets in horizontally_position
(or vertically_position
). Sorry for the redundancy.
After do that I saw the method position_by(coord)
and wish to refactor it a little.
First refactor, using
yield
:
def position_by(end_coor)
position[end_coor]..end_coord(end_coor).map do |x|
yield x
end
end
Then use a
&block
:
def position_by(coord, &block) # ⁵
(position[coord]..end_coord(coord)).map &block
end
⁵ Here we are mapping each position to a & block
, and a block can be [ x , position ( 1) ] or [position ( 0 ) , y] , Where x or and will be replace for the position [ coord ] corresponding.
What does with(&block) mean in Ruby?
It's defined as part of the connection_pool gem which is used by sidekiq, and it's source is below. It looks like it's purpose is to obtain a connection from the pool, yield it to the provided block, and then release the connection back to the pool.
here's how I found that out:
pry> redis = Sidekiq::RedisConnection.create({})
pry> redis.method(:with).source
def with
conn = checkout
begin
yield conn
ensure
checkin
end
end
pry> redis.method(:with).source_location
["./ruby/gems/2.0.0/gems/connection_pool-1.1.0/lib/connection_pool.rb", 46]
And to identify the dependency:
~$ bundle exec gem dependency connection_pool --reverse-dependencies
Gem connection_pool-1.1.0
minitest (>= 5.0.0, development)
Used by
sidekiq-2.16.0 (connection_pool (>= 1.0.0))
Pass block passed to method to another method in Ruby
You can reference the block explicitly
def discard(&block)
self - self.keep(&block)
end
or implicitly
def discard
self - self.keep(&Proc.new {})
end
In your case, I would suggest the first approach.
How does to_enum(:method) receive its block here?
The answer is but a click away: the documentation for Enumerator:
Most [
Enumerator
] methods [but presumably alsoKernel#to_enum
andKernel#enum_for
] have two forms: a block form where the contents are evaluated for each item in the enumeration, and a non-block form which returns a newEnumerator
wrapping the iteration.
It is the second that applies here:
enum = [4, 1, 2, 0].to_enum(:count) # => #<Enumerator: [4, 1, 2, 0]:count>
enum.class # => Enumerator
enum_ewi = enum.each_with_index
# => #<Enumerator: #<Enumerator: [4, 1, 2, 0]:count>:each_with_index>
enum_ewi.class # => Enumerator
enum_ewi.each {|elem, index| elem == index} # => 2
Note in particular irb's return from the third line. It goes on say, "This allows you to chain Enumerators together." and gives map.with_index
as an example.
Why stop here?
enum_ewi == enum_ewi.each.each.each # => true
yet_another = enum_ewi.each_with_index
# => #<Enumerator: #<Enumerator: #<Enumerator: [4, 1, 2, 0]:count>:each_with_index>:each_with_index>
yet_another.each_with_index {|e,i| puts "e = #{e}, i = #{i}"}
e = [4, 0], i = 0
e = [1, 1], i = 1
e = [2, 2], i = 2
e = [0, 3], i = 3
yet_another.each_with_index {|e,i| e.first.first == i} # => 2
(Edit 1: replaced example from docs with one pertinent to the question. Edit 2: added "Why stop here?)
Ruby - Passing Blocks To Methods
The code by David will work fine, but this is an easier and shorter solution:
foo = Proc.new { |prompt| prompt.echo = false }
new_pass = ask("Enter your new password: ", &foo)
verify_pass = ask("Enter again to verify: ", &foo)
You can also use an ampersand to assign a block to a variable when defining a method:
def ask(msg, &block)
puts block.inspect
end
Blocks and yields in Ruby
Yes, it is a bit puzzling at first.
In Ruby, methods can receive a code block in order to perform arbitrary segments of code.
When a method expects a block, you can invoke it by calling the yield
function.
Example:
Take Person
, a class with a name
attribute and a do_with_name
method. When the method is invoked it will pass the name
attribute to the block.
class Person
def initialize( name )
@name = name
end
def do_with_name # expects a block
yield( @name ) # invoke the block and pass the `@name` attribute
end
end
Now you can invoke this method and pass an arbitrary code block.
person = Person.new("Oscar")
# Invoking the method passing a block to print the value
person.do_with_name do |value|
puts "Got: #{value}"
end
Would print:
Got: Oscar
Notice the block receives as a parameter a variable called value
. When the code invokes yield
it passes as argument the value of @name
.
yield( @name )
The same method can be invoked with a different block.
For instance to reverse the name:
reversed_name = ""
# Invoke the method passing a different block
person.do_with_name do |value|
reversed_name = value.reverse
end
puts reversed_name
=> "racsO"
Other more interesting real life examples:
Filter elements in an array:
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
# Select those which start with 'T'
days.select do | item |
item.match /^T/
end
=> ["Tuesday", "Thursday"]
Or sort by name length:
days.sort do |x,y|
x.size <=> y.size
end
=> ["Monday", "Friday", "Tuesday", "Thursday", "Wednesday"]
If the block is optional you can use:
yield(value) if block_given?
If is not optional, just invoke it.
You can try these examples on your computer with irb
(Interactive Ruby Shell)
Here are all the examples in a copy/paste ready form:
class Person
def initialize( name )
@name = name
end
def do_with_name # expects a block
yield( @name ) # invoke the block and pass the `@name` attribute
end
end
person = Person.new("Oscar")
# Invoking the method passing a block to print the value
person.do_with_name do |value|
puts "Got: #{value}"
end
reversed_name = ""
# Invoke the method passing a different block
person.do_with_name do |value|
reversed_name = value.reverse
end
puts reversed_name
# Filter elements in an array:
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
# Select those which start with 'T'
days.select do | item |
item.match /^T/
end
# Sort by name length:
days.sort do |x,y|
x.size <=> y.size
end
Related Topics
Cannot Use Rvm-Installed Ruby With Sudo
Ruby Local Variable Is Undefined
Can't Install Pg Gem on Windows
Ruby Gem For Finding Timezone of Location
Long Running Delayed_Job Jobs Stay Locked After a Restart on Heroku
How to Define Action With Simple Form For
How to Count Duplicates in Ruby Arrays
How to Run All Tests With Minitest
Rake "Already Initialized Constant Wfkv_" Warning
"Gem Install Rails" Fails With Dns Error
Is There a Better Way of Checking Nil or Length == 0 of a String in Ruby
Uninstalling All Gems Ruby 2.0.0
Ruby on Rails Console Is Hanging When Loading