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中的块和产量