Blocks and yields in Ruby

I am trying to understand blocks and yield and how they work in Ruby.

How is yield used? Many of the Rails applications I've looked at use yield in a weird way.

Can someone explain to me or show me where to go to understand them?


Yes, it is a bit puzzling at first.

In Ruby, methods may receive a code block in order to perform arbitrary segments of code.

When a method expects a block, it invokes it by calling the yield function.

This is very handy, for instance, to iterate over a list or to provide a custom algorithm.

Take the following example:

I'm going to define a Person class initialized with a name, and provide a do_with_name method that when invoked, would just pass the name attribute, to the block received.

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name 
        yield( @name ) 
    end
end

This would allow us to call that method and pass an arbitrary code block.

For instance, to print the name we would do:

person = Person.new("Oscar")

#invoking the method passing a block
person.do_with_name do |name|
    puts "Hey, his name is #{name}"
end

Would print:

Hey, his name is Oscar

Notice, the block receives, as a parameter, a variable called name (NB you can call this variable anything you like, but it makes sense to call it name ). When the code invokes yield it fills this parameter with the value of @name .

yield( @name )

We could provide another block to perform a different action. For example, reverse the name:

#variable to hold the name reversed
reversed_name = ""

#invoke the method passing a different block
person.do_with_name do |name| 
    reversed_name = name.reverse
end

puts reversed_name

=> "racsO"

We used exactly the same method ( do_with_name ) - it is just a different block.

This example is trivial. More interesting usages are to filter all the 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, we can also provide a custom sort algorithm, for instance based on the string size:

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["monday", "friday", "tuesday", "thursday", "wednesday"]

I hope this helps you to understand it better.

BTW, if the block is optional you should call it like:

yield(value) if block_given?

If is not optional, just invoke it.


In Ruby, methods can check to see if they were called in such a way that a block was provided in addition to the normal arguments. Typically this is done using the block_given? method but you can also refer to the block as an explicit Proc by prefixing an ampersand ( & ) before the final argument name.

If a method is invoked with a block then the method can yield control to the block (call the block) with some arguments, if needed. Consider this example method that demonstrates:

def foo(x)
  puts "OK: called as foo(#{x.inspect})"
  yield("A gift from foo!") if block_given?
end

foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)

Or, using the special block argument syntax:

def bar(x, &block)
  puts "OK: called as bar(#{x.inspect})"
  block.call("A gift from bar!") if block
end

bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)

It's quite possible that someone will provide a truly detailed answer here, but I've always found this post from Robert Sosinski to be a great explanation of the subtleties between blocks, procs & lambdas.

I should add that I believe the post I'm linking to is specific to ruby 1.8. Some things have changed in ruby 1.9, such as block variables being local to the block. In 1.8, you'd get something like the following:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"

Whereas 1.9 would give you:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"

I don't have 1.9 on this machine so the above might have an error in it.

链接地址: http://www.djcxy.com/p/4388.html

上一篇: 一次捕获多个异常?

下一篇: Ruby中的块和产量