ruby中的关键词yield通常在一个方法中调用,对代码块进行控制,如果没有提供对应的代码块,调用该方法将导致异常。
块(block)是ruby的语法中一部分。
在ruby中当一个block被解析器识别到,该block将被关联到调用它的方法上,并替代方法中的yield关键词。
# mymethod方法中调用yield,所以调用mymethod必须传入一个block参数
def mymethod
puts "this is mymethod before"
yield
puts "this is mymethod after"
end
mymethod
#=> this is mymethod before
#=> LocalJumpError: no block given (yield)
mymethod { puts "this is mymethod"}
#=> this is mymethod before
#=> this is mymethod
#=> this is mymethod after
那如何将mymethod中的block变为可选参数呢?
答案是使用block_given?
def mymethod
puts "this is mymethod before"
yield if block_given?
puts "this is mymethod after"
end
mymethod
#=> this is mymethod before
#=> this is mymethod after
mymethod { puts "this is mymethod"}
#=> this is mymethod before
#=> this is mymethod
#=> this is mymethod after
给yield传参数
def another_method
a = "1"
b = "5"
yield(a, b)
end
another_method {|x, y| puts "First arg is #{x}, Second arg is #{y}"}
#=> First arg is 1, Second arg is 5
给yield传self,即当前对象作为参数
module MyModule
def self.name
@name ||= "Tomi"
end
def self.name=(val)
@name = val
end
def self.mymethod
yield(self) # self表示MyModule
end
end
MyModule.mymethod do |config|
config.name = "Eric"
end
MyModule.name
#=> "Eric"
保存yield返回值
def another_method
a = 1
b = 5
result = yield(a, b)
puts "The result is #{result}"
end
another_method {|x, y| x + y }
#=> The result is 6
重写map方法
# ruby中Array#map使用方法为: [1,2,3].map{|x| x * 2} #=> [2,4,6]
class Array
def mymap
return self.dup unless block_given?
arr = []
self.each do |a| #self is the receiver array
arr << yield(a)
end
arr
end
end
为什么ruby中有时传入block,但是有时传入&block?
block是一个local variable,但是&block传入的是一个该block的引用(reference)
那&block的作用是什么呢?
- 如果该对象是一个block,它将被转化为一个Proc
- 如果该对象是一个Proc,它将被转化为一个block
- 如果该对象是其它类型值,他将调用to_proc,然后转化为一个block.
示例:
def my_method(&my_block)
my_block
end
# 第一种情况
传入的my_block为一个block,通过&符将其转化为一个Proc对象
my_method { "x" } #=> #<Proc:0x00007
#第二种情况
传入一个Proc对象
my_proc = Proc.new { "x" }
my_method(my_proc) #ArgumentError: wrong number of arguments
my_method(&my_proc) #=> #<Proc:0x00007
#第三种情况
传入一个既不是Proc也不是block的参数,而是一个符号 :even?
my_method(&:even?) #=> #<Proc:0x00007
等价于my_method(&:even?.to_proc)
那.map(&:something)怎么工作的呢?
大家熟悉的是该写法等价于.map{|x| x.something}。
简单的说它是.map(&:something.to_proc)的缩写,比如当foo是一个带有to_proc的对象,那你可以使用&foo将其传入一个方法,这样将调用foo.to_proc并且将此作为该方法的block使用
使用默认值来初始化对象:
使用yield(self)来声明对象赋值,在initialize的上下文中,self指向被initialized的对象。
class Car
attr_accessor :color, :doors
def initialize
yield(self)
end
end
car = Car.new do |c|
c.color = "red"
c.doors = 4
end
puts "My car's color is #{car.color}, it has #{car.doors} doors"
总结:
你可以认为block就是一段代码,yield帮助你将这些代码注入一个方法中,这意味着你可以让一个方法按照不同的方式运行,这样你可以重用一个方法做不同的事情。
ref: https://mixandgo.com/learn/mastering-ruby-blocks-in-less-than-5-minutes