初識(shí)block
# 內(nèi)部迭代器
[1, 2, 3].each {|x| puts x }
# 用f(x) = x * 2將原集合映射到一個(gè)新集合
[1, 2, 3].map {|x| x * 2 }
# 挑選出一個(gè)集合里的所有奇數(shù)
[1, 2, 3].select {|x| x%2 != 0 }
# 動(dòng)態(tài)定義一個(gè)問(wèn)候方法
define_method :greet do |name|
puts "hello, #{name}"
end
...
帶block方法的定義
假設(shè)我們自己要定義一個(gè)可以接受block的方法,我們應(yīng)該怎樣來(lái)定義,又怎樣使用傳進(jìn)來(lái)的block呢?
這里假設(shè)我們要給Array
類實(shí)現(xiàn)一個(gè)方法my_each
使之與each
有相同的效果,大概有下面兩種途徑
1.可以直接傳遞給方法, 在方法內(nèi)部可以通過(guò)yield來(lái)調(diào)用傳入的代碼塊:
class Array
def my_each
i = 0
if block_given?
while i < self.size
yield self[i]
i += 1
end
end
self
end
end
[1, 2, 3].my_each {|x| puts x }
這里通過(guò)block_given?
方法判斷調(diào)用者是否給方法傳遞了block, 通過(guò)yield
調(diào)用傳入的block并將參數(shù)傳遞給block,整個(gè)過(guò)程都不直接與block打交道而是用block_given?
和yield
這種專門處理block的方式
2.block可以和Proc對(duì)象相互轉(zhuǎn)換, 變成對(duì)象之后,可以像其他對(duì)象一樣被傳遞,被返回,被復(fù)制等
# 代碼塊被轉(zhuǎn)換成了Proc的對(duì)象p
p = Proc.new {|x| puts x }
# p叫做可調(diào)用對(duì)象,可以通過(guò)call方法被調(diào)用
p.call('hello')
# 語(yǔ)法糖, 和上面的call等效
p.('hello')
p['hello']
那么可以這樣來(lái)實(shí)現(xiàn)my_each方法:
class Array
def my_each(p=nil)
i = 0
unless p.nil?
while i < self.size
p.call(self[i])
i += 1
end
end
self
end
end
p = Proc.new{|x| puts x}
[1, 2, 3].my_each(p)
這樣做是把p完全當(dāng)成了方法的普通參數(shù), 因此必須要先把block轉(zhuǎn)換成Proc對(duì)象才能傳入, 這與Array#each
相比太麻煩了,幸好Ruby提供了語(yǔ)法糖:
class Array
def my_each(&p)
i = 0
unless p.nil?
while i < self.size
p.call(self[i])
i += 1
end
end
self
end
end
[1, 2, 3].my_each {|x| puts x }
如果將參數(shù)寫成&p
這樣的形式并且放在參數(shù)表的最末,那么方法接受到block之后就會(huì)自動(dòng)把block轉(zhuǎn)換成Proc對(duì)象并且存進(jìn)p中,然后在方法內(nèi)部我們就知道p是一個(gè)Proc對(duì)象,就可以按照之前的思路處理了
&
是一個(gè)運(yùn)算符, 這個(gè)運(yùn)算符總是會(huì)把Proc對(duì)象轉(zhuǎn)換成一個(gè)block.由于block對(duì)于Ruby程序員總是不可見的,所以不能單獨(dú)使用它,但卻可以把block當(dāng)作參數(shù)傳遞給方法:
p = Proc.new{|x| puts x }
[1, 2, 3].my_each(&p)
block的語(yǔ)法糖
# 將字符串列表里的每個(gè)元素都轉(zhuǎn)換成整數(shù)
['1', '2', '3'].map{|x| x.to_i } # => [1, 2, 3]
# 求列表里所有數(shù)的和
[1, 2, 3].reduce(0) {|x, y| x + y } # => 6
實(shí)際上還有更簡(jiǎn)單的寫法:
['1', '2', '3'].map(&:to_i) # => [1, 2, 3]
[1, 2, 3].reduce(0, &:+)
之所以能這么做,是因?yàn)橛?code>Symbol#to_proc這個(gè)方法的存在
to_proc
會(huì)返回一個(gè)Proc對(duì)象:
to_i = :to_i.to_proc
to_i.call('1') # => 1
add = :+.to_proc
add.call(1, 2) # => 3
一個(gè)實(shí)現(xiàn)了to_proc
方法的對(duì)象如果和&
運(yùn)算符結(jié)合,會(huì)導(dǎo)致對(duì)象的to_proc
方法被調(diào)用,然后被&
轉(zhuǎn)換成block傳遞進(jìn)方法
['1', '2', '3'].map(&:to_i)
# 相當(dāng)于以下兩步
to_i = :to_i.to_proc
['1', '2', '3'].map(&to_i)
想象一下Symbol#to_proc
的實(shí)現(xiàn)
class Symbol
def to_proc
Proc.new {|x| x.send(self)}
end
end
思考題: 如果to_proc
要支持多個(gè)參數(shù),比如上面的[1, 2, 3].reduce(0, &:+)
,要怎么實(shí)現(xiàn)?