Ruby 匿名函數的使用詳解

來源:互聯網
上載者:User

Ruby 裡面主要的可調用的對象是 Proc 對象,Lambdas,方法對象。Proc 是獨立的代碼序列,你可以建立,儲存,可以作為方法的參數,你願意的話,也可以使用 call 方法執行它。Lambdas 跟 Proc 對象很像,Lambda 其實就是 Proc 對象,不過稍有不同。

Proc 對象

用 Proc.new 建立一個 Proc 執行個體:

pr = Proc.new { puts "inside a proc's block" }
上面的代碼塊就是 Proc 的主體,調用 Proc 的時候會執行代碼塊裡的東西:

pr.call
結果是:

inside a proc's block
給 proc 方法一個代碼塊,它會給你返回一個 Proc 對象。

proc { puts "hi!" }
Procs 與 Blocks

不是所有的代碼塊都跟 Proc 一樣。

[1,2,3].each {|x| puts x * 10 }
上面用了個代碼塊,但是並沒有建立一個 proc。

一個方法可以捕獲一個代碼塊:

def call_a_proc(&block)
  block.call
end

call_a_proc { puts "I'm the block ... or Proc ... or someting." }
輸出的是:

I'm the block ... or Proc ... or someting.
用 proc 也行:

p = Proc.new {|x| puts x.upcase}
%w{ Matt Damon }.each(&p)
輸入的是:

MATT
DAMON
文法(blocks)與對象(procs)

Ruby 的代碼塊不是一個對象。

[1,2,3].each {|x| puts x * 10}
接收者是一個對象,代碼塊不是。代碼塊是調用方法文法的一部分。把代碼塊想成是一個參數列表。

puts c2f(100)
上面調用的方法裡,參數是對象,但整體的參數列表並不是對象:(100)。沒有 ArgumentList 類,也沒有 CodeBlock 類。

block 與 proc 轉換

def capture_block(&block)
  block.call
end

capture_block { puts "inside the block" }
上面隱式調用了 Proc.new,使用同樣的區塊。

解釋:

1 調用 capture_block 方法,給它提供一個代碼塊:

capture_block { puts "inside the block" }
2 使用同樣的區塊建立了一個 Proc 對象:

Proc.new { puts "inside the block" }
3 block 參數綁定了 Proc 對象。

def capture_block(&block)
  puts "got block as proc"
  block.call
end
Proc 作為代碼塊

p = Proc.new { puts "this proc argument will serve as a code block" }
capture_block(&p)
輸出的是:

this proc argument will serve as a code block
用 & 符號標記的 proc 會作為一個代碼塊,所以你就不能再給同一個方法提供一個代碼塊了。執行下面的代碼會報錯:

capture_block(&p) { puts "this is the explicit block" }
提示:“both block arg and actual block given”。Ruby 不能判斷你到底想用 proc 還是 block 作為代碼塊。你只能選擇一個。

&p 裡的 & 是 to_proc 方法的封裝。在 Proc 對象上調用 to_proc 會返回 Proc 對象本身。

你仍然需要 &,如果你想做:

capture_block(p)
或者:

capture_block(p.to_proc)
這樣做你傳遞的只是一般的參數。沒有讓 proc 參數作為代碼塊。也就是說在 capture_block(&p) 裡面,這個 & 有兩個意思,它會觸發在 p 上執行 to_proc 方法,還會讓 Ruby 把執行了 to_proc 返回的 proc 對象當成是一個代碼塊。

Symbol#to_proc

>> %w{ a b }.map(&:capitalize)
=> ["A", "B"]
:capitalize 這個符號會被解釋成發送給數組裡面每個項目的資訊。相當於:

%w{ a b }.map {|str| str.capitalize}
也相當於:

%w{ a b }.map {|str| str.send(:capitalize)}
可以去掉括弧:

%w{ a b }.map &:capitalize
Procs 作為閉包

在方法主體裡用的本地變數,跟調用方法的時候使用的本地變數不一樣。

def talk
  a = "hello"
  puts a
end

a = "goodbye"
# 輸出的是 hello
talk

# 輸出的是 goodbye
puts a
a 這個標識符被分配使用了兩次,不過兩次分配之間沒什麼聯絡。

在代碼塊裡使用已經存在的變數:

>> m = 10
=> 10
>> [1,2,3].each {|x| puts x * m}
10
20
30
=> [1, 2, 3]
multiply_by 返回了一個 proc,調用 multiply_by 方法的時候,傳遞給這個方法的參數,會保留在 proc 裡面:

def multiply_by(m)
  Proc.new {|x| puts x * m}
end
mult = multiply_by(10)

# 輸出 120
mult.call(12)
再做個實驗,注意下面兩個變數 a:

def call_some_proc(pr)
  a = "在方法範圍裡的 'a'"
  puts a
  pr.call
end

a = "在 Proc 區塊裡使用的 'a'"
pr = Proc.new { puts a }
pr.call
call_some_proc(pr)
輸出的結果是:

在 Proc 區塊裡使用的 'a'
在方法範圍裡的 'a'
在 Proc 區塊裡使用的 'a'
Proc 對象會帶著它的上下文。在上面的例子裡,a 就是這個上下文裡的一個部分,這個 a 會一直在 Proc 裡存在。像這樣的一塊代碼,一直帶著它建立時的上下文,就是一個閉包(closure)。調用閉包,就會開啟閉包,它裡面會包含你建立它的時候放進去的東西。閉包會保留程式啟動並執行部分狀態。

建立一個閉包,有點像是打包行李,不管你在哪裡開啟這個行李包,它裡面都會包含你打包的時候放進去的東西。
看一下計數器的例子,每次調用 proc 的時候它的變數的值都會增加一:

def make_counter
  n = 0
  return Proc.new { n += 1 }
end

c = make_counter
puts c.call
puts c.call
d = make_counter
puts d.call
puts c.call
輸出的是:

1
2
1
3
Proc 參數

一個 Proc,帶個區塊,區塊裡有個參數:

pr = Proc.new {|x| puts "調用時的參數:#{x}" }
pr.call(100)
輸出的是:

調用時的參數:100
Proc 的參數與方法處理參數的方式不一樣。它不乎調用時使用的參數的數量是否正確。支援一個參數:

>> pr = Proc.new {|x| p x }
=> #<Proc:0x007faf7aa016e0@(irb):27>
調用時不使用參數:

>> pr.call
nil
調用時使用了多個參數,只取第一個參數,扔掉剩下的:

>> pr.call(1,2,3)
1
用 lambda 與 -> 建立函數

lambda 方法返回一個 Proc 對象。 提供的代碼塊會成為函數的主體:

>> lam = lambda { puts "a lambda!" }
=> #<Proc:0x007faf7a8afd28@(irb):66 (lambda)>
>> lam.call
a lambda!
=> nil
lambda 口味的 proc 與一般的 proc 有三個不一樣的地方。lambda 需要顯式建立,隱式建立的不會是 lambda,比如像這樣:

def m(&block)
lambda 對待 return 關鍵詞與普通的 proc 也不一樣。

def return_test
  l = lambda { return }
  l.call
  puts "still here!"
  p = Proc.new { return }
  p.call
  puts "you won't see this message!"
end

return_test
輸出的是  “still here! ”,不會看到第二條資訊。因為調用 Proc 對象會觸發從 return_test 那裡返回。不過調用 lambda 觸發的是從 lambda 主體的返回(退出),方法的執行仍會繼續。

lambda 口味的 proc ,調用的時候要使用正常數量的參數:

>> lam = lambda {|x| p x}
=> #<Proc:0x007faf7a998c58@(irb):105 (lambda)>
>> lam.call(1)
1
=> 1
>> lam.call
ArgumentError: wrong number of arguments (given 0, expected 1)
 from (irb):105:in `block in irb_binding'
 from (irb):107
 from /usr/local/bin/irb:11:in `<main>'
>> lam.call(1,2,3)
ArgumentError: wrong number of arguments (given 3, expected 1)
 from (irb):105:in `block in irb_binding'
 from (irb):108
 from /usr/local/bin/irb:11:in `<main>'
->

>> lam = -> { puts "hi" }
=> #<Proc:0x007faf7a903400@(irb):109 (lambda)>
>> lam.call
hi
=> nil
參數放到括弧裡:

>> mult = ->(x,y) { x * y }
=> #<Proc:0x007faf7a980720@(irb):111 (lambda)>
>> mult.call(3,4)
=> 12
作為對象的方法

方法不是對象,不過你可以對象化(objectify)它們,讓它們成為對象。

捕獲方法對象

使用 method 方法,把方法的名字作為它的參數。

class C
  def talk
    puts "擷取方法對象,self 是 #{self}。"
  end
end

c = C.new
meth = c.method(:talk)
meth 是方法對象,綁定了 c 對象的 talk 方法。調用它:

>> meth.call
擷取方法對象,self 是 #<C:0x007faf7a950f48>。
重新綁定:

class D < C
end

d = D.new
unbound = meth.unbind
unbound.bind(d).call
結果是:

擷取方法對象,self 是 #<D:0x007faf7a84c430>。
也可以這樣重新綁定:

unbound = C.instance_method(:talk)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.