淺談ruby語言中的一些概念(lambda, proc, block)


本文參考於 ruby-china: 聊聊 Ruby 中的 block, proc 和 lambda,是一篇讀后小結,主要便於自己理解.侵刪

block 和 Proc


>> #### ruby中的block是方法的一個重要但非必要的組成部分,任何方法后面都可以掛載一個block,如果你定義的方法想使用block做點事情.那么你需要使用yield關鍵字或者&p. ```ruby def f1 puts 'f1' end

def f2(&p)
puts 'f2'
p.call if block_given?
end

def f3
puts 'f3'
yield if block_given?
end

f1 ## 'f1', 普通方法調用不掛載塊時
f1 {puts 'block'} ## 'f1' 普通方法調用掛載塊時

f2 ## 'f2', 如果沒有 判斷 if block_given?, 將會報錯
f2 {puts 'block'} ## 'f2' 'block', 掛載了塊作為參數&p傳入,p是一個proc,可以用關鍵字 call 來執行

f3 ## 'f2', 如果沒有 判斷 if block_given?, 將會報錯
f3 {puts 'block'} ## 'f2' 'block',掛載了塊,用關鍵字 yield觸發

>> #### 我們可以看到,任何方法都可以掛載一個block.
>> #### 要觸發block執行時,用關鍵字 yield會執行傳入的塊.
>> #### 也可以通過在參數里定義&p, 傳入block為一個參數,用call去觸發.
>> #### ***block就是一段綁定了當前作用域變量代碼,call的時候就是當前位置嵌入了代碼***
<hr>
>> #### 那么block作為參數傳入方法后是一個什么對象呢?既然是參數,是不是可以再作為參數傳遞給其他方法呢?
```ruby
def block_args(&p)
  puts p.class
  puts p
  p.call
  block_yield &p 
end

def block_yield
  puts 'yield'
  yield
end

block_args {puts 'hahaha'} 
## 'Proc' 
## #<Proc:0x007fb2f3ae5da0@(irb):334>
## 'hahaha'
## 'yield'
## 'hahaha'

由上可見,block作為參數傳入方法后是一個Proc的實例.call是proc的method,傳入的塊是&p, 在方法里 p是proc, 如果要作為參數再進行傳遞,應該使用塊 即 &p.

記住這句話: "&p是block, p是proc",一般掛載在方法后面的是block,proc是個'幕后工作者',一般不會顯式地創建它.

yield和&p的用法類似,&p參數傳入作為c可以作為參數再傳遞,yield無需在方法上定義參數,使用方便卻也無法再傳遞.


>> ### 那么理解了這個之后再回過頭來看一些我們常用的技巧時,例如: ```ruby ['1', '2', '3', '4'].map(&:to_i) ``` >> #### 此處的&:to_i 類比為 &p的話,符號&會觸發:to_i的to_proc方法,to_proc執行后會返回一個proc實例,然后&會把這個proc實例轉換成一個block. >> #### 我們需要要明白map方法后掛的是一個block,而不是接收一個proc對象做為參數. >> #### &:to_i是一個block,block不能獨立存在,同時你也沒有辦法直接存儲或傳遞它,必須把block掛在某個方法后面. ```ruby ## :to_i是一個Symbol實例 class Symbol def to_proc Proc.new {|obj| obj.send(self) } end end

所以 map(&:to_i)也應該是轉化為 map{|i| i.to_i }

['1', '2', '3', '4'].map{|i| i.to_i }

map 方法后面 傳入的參數是個block.

>> ###  做個小結,block和proc是兩種不同的東西,block有形無體,proc可以將block實體化,可以把&p看做一種運算,其中&觸發p的to_proc方法,然后&會將to_proc方法返回的proc對象轉換成block.
<hr><hr>
> ## lambda
>> #### lambda是***匿名方法***,lambda和proc也是兩種不同的東西,但是在ruby中lambda只能依附proc而存在,這點和block不同,block並不依賴proc.
<hr>
>> #### 在ruby里創建一個Proc的實例對象有如下幾種方法:
```ruby
Proc.new {} ## #<Proc:0x007fb2f39fd0f0@(irb):367>
proc {}  ## #<Proc:0x007fb2f3a02a50@(irb):368>
lambda {}  ## #<Proc:0x007fb2f3a00070@(irb):369 (lambda)>
-> {}  ## #<Proc:0x007fb2f41c9838@(irb):370 (lambda)>
  • 我們驚奇地發現在ruby里要創建一個Proc的方法居然有四種,不管是lambda,還是proc,亦或者是-> 居然都是Proc的實例.但是我們仔細看還是可以看出 Proc.new 和 proc 應該是一樣的,區別於 lambda 和 -> (都含有 (lambda)). 所以我們說proc和lambda是不一樣的

那么具體哪些地方不一樣呢?先來幾個經典的例子:

## 首先我們在上面說了 proc來源用& 符號來觸發轉換成block
def f(p)
  instance_eval &p
  1
end
p1 = Proc.new {}
p2 = proc {}
l1 = lambda {}

f(p1) # 1
f(p2) # 1
f(l1) # ArgumentError wrong number of arguments (1 for 0)
f(l2) # ArgumentError wrong number of arguments (1 for 0)
  • proc 和 lambda 第一處不同點是,proc可以用&轉化成block,而lambda不行.

p = proc{return 0}
l = lambda {return 0}
p.call ##  LocalJumpError: unexpected return
l.call ##  0

#############

def f0()
  p = Proc.new {return 0}
  p.call
  1
end

def f1()
  p = proc { return 0 }
  p.call
  1
end

def f2()
  l = lambda { return 0}
  l.call
  1
end

def f3()
  l = -> { return 0 }
  l.call
  1
end

f0  ## 0
f1  ## 0
f2  ## 1
f2  ## 1
  • proc 和 lambda 第二處不同點是對return關鍵字的處理不同.

  • proc的return只能在方法體里執行,proc單獨call會報錯,lambda則可以單獨執行call.

  • 方法體里的 proc 對return敏感,執行時如果有return則中斷跳出方法體,不會再繼續往下執行.

  • 而 lambda 的執行更像是調用了某個方法,方法體里return返回后會繼續往下執行.

p = proc {|a, b| puts 'proc args'}
l = lambda {|a, b| puts 'lambda args'}
p.call(1, 2) ## proc args
l.call(1, 2) ## lambda args
p.call ## proc args
l.call ## ArgumentError: wrong number of arguments (0 for 2)

proc 和 lambda 第三處不同點是對對參數的檢查.proc 不檢查參數,lambda檢查參數.


> ## 小結 >> ### 做個小結,lambda和proc是Proc的兩種不同實例.proc是在方法體的當前上下文處執行一段block代碼的.而lambda更像是一個方法,將block作為一個方法,call的時候去調用這個方法. >> ### lambda 檢查參數, return時正常返回, proc 不檢查參數,return 時中斷方法體.lambda更像是一個方法.匿名方法.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM