Ruby: Code Block

代碼塊是什么栈戳?

代碼塊是由 {...}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)化

blockproc 是可以通過 & 符號(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匕垫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子虐呻,更是在濱河造成了極大的恐慌象泵,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斟叼,死亡現(xiàn)場離奇詭異偶惠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)朗涩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門忽孽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谢床,你說我怎么就攤上這事兄一。” “怎么了识腿?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵出革,是天一觀的道長。 經(jīng)常有香客問我渡讼,道長骂束,這世上最難降的妖魔是什么耳璧? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮展箱,結(jié)果婚禮上旨枯,老公的妹妹穿的比我還像新娘。我一直安慰自己析藕,他們只是感情好召廷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著账胧,像睡著了一般竞慢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上治泥,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天筹煮,我揣著相機(jī)與錄音,去河邊找鬼居夹。 笑死败潦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的准脂。 我是一名探鬼主播劫扒,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狸膏!你這毒婦竟也來了沟饥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬榮一對情侶失蹤湾戳,失蹤者是張志新(化名)和其女友劉穎贤旷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砾脑,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幼驶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了韧衣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盅藻。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖畅铭,靈堂內(nèi)的尸體忽然破棺而出萧求,到底是詐尸還是另有隱情,我是刑警寧澤顶瞒,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布夸政,位于F島的核電站,受9級(jí)特大地震影響榴徐,放射性物質(zhì)發(fā)生泄漏守问。R本人自食惡果不足惜匀归,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耗帕。 院中可真熱鬧穆端,春花似錦、人聲如沸仿便。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗽仪。三九已至荒勇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闻坚,已是汗流浹背沽翔。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窿凤,地道東北人仅偎。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像雳殊,于是被迫代替她去往敵國和親橘沥。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容