代碼塊是什么栈戳?
代碼塊是由 {...}
或 do..end
包圍起來的一塊代碼。
代碼塊通常用于實(shí)現(xiàn)自定義的運(yùn)算碍粥,有點(diǎn)像匿名方法害晦。
代碼塊也可以作為Proc
對象賦值給指定的變量(非匿名)。
因?yàn)橐陨咸匦曰躺担a塊提供了函數(shù)式編程的特性棍郎,如閉包等。
匿名與非匿名代碼塊
我們默認(rèn) block 為匿名代碼塊银室,proc 為非匿名代碼塊涂佃。
使用 proc 的好處是它可以存放在變量中,以便于重復(fù)使用蜈敢。
block 通過 yield 關(guān)鍵字來調(diào)用辜荠,proc 通過 call 方法來調(diào)用。
block 跟在方法調(diào)用后(單次)抓狭,proc 可作為參數(shù)多次傳入方法中(多次)伯病。
# implicit code block
def calculation(a, b)
yield(a, b)
end
calculation(5, 6) { |a, b| a + b } # addition block #=> 11
calculation(5, 6) { |a, b| a - b } # subtraction block #=> -1
# explicit code block (lambda)
def calculation(a, b, operation)
operation.call(a, b)
end
addition_block = lambda { |a, b| a + b } # addition block
subtraction_block = lambda { |a, b| a - b } # subtraction block
calculation(5, 6, addition_block) # addition #=> 11
calculation(5, 6, subtraction_block) # subtraction #=> -1
匿名與非匿名的相互轉(zhuǎn)化
block 和 proc 是可以通過 &
符號(hào)相互轉(zhuǎn)化的:
開頭帶&
符號(hào)的整體 (&foo
) 可以看做是一個(gè) block,
通過引用去掉&
符號(hào)后的 foo
變量及為它對應(yīng)的 proc 對象否过。
# implicit -> explicit
def calculation1(a, b, &block)
block.class #=> Proc
block.call(a, b)
end
calculation1(1, 2) { |a, b| a + b } # => 3
addition_lambda = -> (a, b) { a + b }
calculation1(1, 2, &addition_lambda) # => 3
# explicit -> implicit
def calculation2(a, b)
yield(a, b) if block_given?
end
calculation2(5, 5) { |a, b| a + b } # => 10
addition = -> (a, b) { a + b }
calculation2(5, 5, &addition) # => 10
lambda vs proc
相同點(diǎn)
二者都是 Proc
類的對象
proc { 'foo' }.class # => Proc
lambda { 'bar' }.class # => Proc
lambda 有語法糖午笛,上面可以簡寫成 -> { 'bar' }
proc 也可以通過 Proc.new { 'foo' }
實(shí)例化對象。
不同點(diǎn)
lambda 要比 proc 更加嚴(yán)格苗桂,更像是匿名方法季研。
1.返回值 return
,lambda 從當(dāng)前代碼塊返回誉察,proc 從當(dāng)前代碼塊所在的作用域返回与涡。
2.代碼塊參數(shù)檢驗(yàn),lambda 參數(shù)檢驗(yàn)更加嚴(yán)格持偏,多了少了都會(huì)報(bào)錯(cuò)驼卖,而 proc 則不會(huì)。
我們可以通過調(diào)用 Proc#lambda?
來區(qū)分他們鸿秆。
Tips: block/proc/lambda
都可使用 next
關(guān)鍵字退出代碼塊酌畜,與在 lambda 中 return
的用法相同。由于怪異的返回語法卿叽,在 proc 中盡量避免使用 return
桥胞,除非你確實(shí)熟練掌握了它恳守,并且真的需要使用這個(gè)特性。
def a_method
puts lambda { return "we just returned from the block" }.call
puts "we just returned from the calling method"
end
a_method
#---output---
# we just returned from the block
# we just returned from the calling method
def a_method
puts proc { return "we just returned from the block" }.call
puts "we just returned from the calling method"
end
result = a_method
puts result
#---output---
# we just returned from the block
什么是閉包(Closure)
閉包是函數(shù)式編程中的一個(gè)重要特性贩虾。
我的理解就是他能穿透作用域催烘,訪問代碼塊上下文的局部變量。
Wikipedia: ‘Closure is a function or a reference to a function together with a referencing environment. Unlike a plain function, closures allow a function to access non-local variables even when invoked outside of its immediate lexical scope.’
代碼塊的其他實(shí)用技巧
Kernel#block_given?
: 這個(gè)方法用來判斷該方法在調(diào)用時(shí)有沒有接受到匿名代碼塊 (注意:這里僅用于判斷是否有匿名代碼塊傳入缎罢,當(dāng)未傳入代碼塊或傳入的是非匿名代碼塊時(shí):block_given
返回false
)伊群。最佳應(yīng)用場景:使用它的目的通常是在你用yield
之前判斷是否有匿名代碼塊可以操作。如果未添加匿名代碼塊且使用yield
關(guān)鍵字策精,解釋器會(huì)拋出LocalJumpError
的異常錯(cuò)誤并伴隨著錯(cuò)誤提示no block is given
舰始。所以每次使用yield
前都確保用:block_given
來判斷,這樣就沒事了 :D匿名代碼塊語法糖:
(1..3).map(&:to_s) #=> ["1", "2", "3"]
&
會(huì)觸發(fā)后面的 symbol:to_s
調(diào)用自身的Symbol#to_proc
方法咽袜,再通過&
將 proc 轉(zhuǎn)化為一個(gè)匿名的 block 傳給 map 方法丸卷。
Play with Code Block
- 將非匿名代碼塊作為參數(shù)傳入方法中,再轉(zhuǎn)化為匿名代碼塊使用询刹。
def filter(array, block)
array.select(&block)
end
arr = [1, 2, 3 ,4 ,5]
choose = -> (n) { n < 3}
filter arr, choose # => [1, 2]
# explain: 'select' should receive implicit code block,
# the second parameter of the 'filter; method is lambda,
# so use '&block' to convert lambda to implicit block.
- 將匿名代碼塊傳入非匿名代碼塊中谜嫉,依然作為匿名代碼塊使用。
filter_block = lambda do |array, &block|
array.select(&block)
end
filter_block.call([1, 2, 3, 4]) { |number| number.even? } #=> [2, 4]
filter_block.call([1, 2.0, 3, 4.0]) { |number| number.integer? } #=> [1, 3]
- 將方法轉(zhuǎn)化為 proc 對象范抓,再轉(zhuǎn)化為匿名代碼塊被方法使用。
def say; yield; end
def hi; "hi"; end
def hello; "hello"; end
say &method(:hi).to_proc # => "hi"
say &method(:hello).to_proc # => "hello"
- 方法轉(zhuǎn)化為代碼塊傳入其他方法中使用食铐。
def plus a, b
a + b
end
def plusplus a, b, c, d, &plus
block_given? ? yield(a+b, c+d) : 0
end
plusplus 1, 2, 3, 4, &method(:plus).to_proc # => 10
- 計(jì)算器小程序
# complex example
def calculation(*operations, calculator_lambda)
operations.each do |operation|
num1 = operation[0]
num2 = operation[1]
operator = operation[2]
puts calculator_lambda.call(num1, num2, operator)
end
end
#
calculator_lambda = -> (a, b, op) do
next a + b if op == '+'
next a - b if op == '-'
next a * b if op == '*'
next a / b if op == '/'
raise 'invalid operator'
end
#
operation1 = [1, 2, '+'] # 1 + 2 = 3
operation2 = [6, 3, '/'] # 6 / 3 = 2
calculation(operation1, operation2, calculator_lambda)
#
#---output---
# 3
# 2