本文簡要介紹ruby語言中 Proc類
的用法。
Proc
對象是對代碼塊的封裝,它可以存儲在局部變量中,傳遞給方法或另一個 Proc
,並且可以被調用。 Proc
是 Ruby 中的一個基本概念,也是其函數式編程特性的核心。
square = Proc.new {|x| x**2 }
square.call(3) #=> 9
# shorthands:
square.(3) #=> 9
square[3] #=> 9
Proc
對象是 closures
,這意味著它們可以記住並且可以使用創建它們的整個上下文。
def gen_times(factor)
Proc.new {|n| n*factor } # remembers the value of factor at the moment of creation
end
times3 = gen_times(3)
times5 = gen_times(5)
times3.call(12) #=> 36
times5.call(5) #=> 25
times3.call(times5.call(4)) #=> 60
創建
有幾種方法可以創建 Proc
-
使用
Proc
類構造函數:proc1 = Proc.new {|x| x**2 }
-
使用
Kernel#proc
方法作為Proc.new
的簡寫:proc2 = proc {|x| x**2 }
-
將一段代碼接收到 proc 參數中(注意
&
):def make_proc(&block) block end proc3 = make_proc {|x| x**2 }
-
使用
Kernel#lambda
方法構造具有 lambda 語義的 proc(有關 lambda 的說明,請參見下文):lambda1 = lambda {|x| x**2 }
-
使用 Lambda proc literal 語法(也構造具有 lambda 語義的 proc):
lambda2 = ->(x) { x**2 }
Lambda 和非 Lambda 語義
Procs 有兩種形式:lambda 和 non-lambda(常規 procs)。區別在於:
-
在 lambdas 中,
return
和break
表示退出此 lambda; -
在非 lambda 過程中,
return
表示退出包含方法(如果在方法之外調用,將拋出LocalJumpError
); -
在非 lambda 過程中,
break
表示退出給定塊的方法。 (如果在方法返回後調用,將拋出LocalJumpError
); -
在 lambdas 中,參數的處理方式與方法相同:嚴格,
ArgumentError
用於參數編號不匹配,並且沒有額外的參數處理; -
常規 proc 更慷慨地接受參數:缺少的參數用
nil
填充,如果 proc 有多個參數,則單個Array
參數將被解構,並且額外參數不會引發錯誤。
例子:
# +return+ in non-lambda proc, +b+, exits +m2+.
# (The block +{ return }+ is given for +m1+ and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { return }; $a << :m2 end; m2; p $a
#=> []
# +break+ in non-lambda proc, +b+, exits +m1+.
# (The block +{ break }+ is given for +m1+ and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { break }; $a << :m2 end; m2; p $a
#=> [:m2]
# +next+ in non-lambda proc, +b+, exits the block.
# (The block +{ next }+ is given for +m1+ and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1 { next }; $a << :m2 end; m2; p $a
#=> [:m1, :m2]
# Using +proc+ method changes the behavior as follows because
# The block is given for +proc+ method and embraced by +m2+.
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { return }); $a << :m2 end; m2; p $a
#=> []
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { break }); $a << :m2 end; m2; p $a
# break from proc-closure (LocalJumpError)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&proc { next }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]
# +return+, +break+ and +next+ in the stubby lambda exits the block.
# (+lambda+ method behaves same.)
# (The block is given for stubby lambda syntax and embraced by +m2+.)
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { return }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { break }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]
$a = []; def m1(&b) b.call; $a << :m1 end; def m2() m1(&-> { next }); $a << :m2 end; m2; p $a
#=> [:m1, :m2]
p = proc {|x, y| "x=#{x}, y=#{y}" }
p.call(1, 2) #=> "x=1, y=2"
p.call([1, 2]) #=> "x=1, y=2", array deconstructed
p.call(1, 2, 8) #=> "x=1, y=2", extra argument discarded
p.call(1) #=> "x=1, y=", nil substituted instead of error
l = lambda {|x, y| "x=#{x}, y=#{y}" }
l.call(1, 2) #=> "x=1, y=2"
l.call([1, 2]) # ArgumentError: wrong number of arguments (given 1, expected 2)
l.call(1, 2, 8) # ArgumentError: wrong number of arguments (given 3, expected 2)
l.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
def test_return
-> { return 3 }.call # just returns from lambda into method body
proc { return 4 }.call # returns from method
return 5
end
test_return # => 4, return from proc
Lambda 可用作 self-sufficient 函數,尤其可用作高階函數的參數,其行為與 Ruby 方法完全相同。
Procs 對於實現迭代器很有用:
def test
[[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
end
在 map
內部,代碼塊被視為常規(非 lambda)proc,這意味著內部數組將被解構為成對的參數,並且 return
將從方法 test
退出。使用更嚴格的 lambda 是不可能的。
您可以使用 lambda?
實例方法從常規過程中區分 lambda。
Lambda 語義通常在 proc 生命周期內保留,包括 &
-deconstruction 到代碼塊:
p = proc {|x, y| x }
l = lambda {|x, y| x }
[[1, 2], [3, 4]].map(&p) #=> [1, 3]
[[1, 2], [3, 4]].map(&l) # ArgumentError: wrong number of arguments (given 1, expected 2)
唯一的例外是動態方法定義:即使通過傳遞非 lambda 過程定義,方法仍然具有參數檢查的正常語義。
class C
define_method(:e, &proc {})
end
C.new.e(1,2) #=> ArgumentError
C.new.method(:e).to_proc.lambda? #=> true
此異常確保方法永遠不會有異常的參數傳遞約定,並且可以輕鬆地讓包裝器定義行為正常的方法。
class C
def self.def2(name, &body)
define_method(name, &body)
end
def2(:f) {}
end
C.new.f(1,2) #=> ArgumentError
包裝器def2
接收 body
作為非 lambda 過程,但定義了具有正常語義的方法。
將其他對象轉換為 proc
任何實現to_proc
方法的對象都可以通過&
運算符轉換為proc,因此可以被迭代器使用。
class Greeter
def initialize(greeting)
@greeting = greeting
end
def to_proc
proc {|name| "#{@greeting}, #{name}!" }
end
end
hi = Greeter.new("Hi")
hey = Greeter.new("Hey")
["Bob", "Jane"].map(&hi) #=> ["Hi, Bob!", "Hi, Jane!"]
["Bob", "Jane"].map(&hey) #=> ["Hey, Bob!", "Hey, Jane!"]
在 Ruby 核心類中,此方法由 Symbol
、 Method
和 Hash
實現。
:to_s.to_proc.call(1) #=> "1"
[1, 2].map(&:to_s) #=> ["1", "2"]
method(:puts).to_proc.call(1) # prints 1
[1, 2].each(&method(:puts)) # prints 1, 2
{test: 1}.to_proc.call(:test) #=> 1
%i[test many keys].map(&{test: 1}) #=> [1, nil, nil]
孤立的 Proc
塊中的return
和break
退出方法。如果從塊中生成 Proc
對象並且 Proc
對象在方法返回之前仍然存在,則return
和break
無法工作。在這種情況下,return
和 break
引發 LocalJumpError
。在這種情況下, Proc
對象稱為孤立的 Proc
對象。
請注意,return
和 break
的退出方法不同。有一種情況是孤立的 break
但不是孤立的 return
。
def m1(&b) b.call end; def m2(); m1 { return } end; m2 # ok
def m1(&b) b.call end; def m2(); m1 { break } end; m2 # ok
def m1(&b) b end; def m2(); m1 { return }.call end; m2 # ok
def m1(&b) b end; def m2(); m1 { break }.call end; m2 # LocalJumpError
def m1(&b) b end; def m2(); m1 { return } end; m2.call # LocalJumpError
def m1(&b) b end; def m2(); m1 { break } end; m2.call # LocalJumpError
由於 return
和 break
在 lambdas 中退出塊本身,因此 lambdas 不能被孤立。
編號參數
編號參數是隱式定義的塊參數,旨在簡化編寫短塊:
# Explicit parameter:
%w[test me please].each { |str| puts str.upcase } # prints TEST, ME, PLEASE
(1..5).map { |i| i**2 } # => [1, 4, 9, 16, 25]
# Implicit parameter:
%w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE
(1..5).map { _1**2 } # => [1, 4, 9, 16, 25]
支持從 _1
到 _9
的參數名稱:
[10, 20, 30].zip([40, 50, 60], [70, 80, 90]).map { _1 + _2 + _3 }
# => [120, 150, 180]
不過,建議明智地使用它們,可能會將自己限製在 _1
和 _2
以及 one-line 塊中。
編號參數不能與顯式命名的參數一起使用:
[10, 20, 30].map { |x| _1**2 } # SyntaxError (ordinary parameter is defined)
為避免衝突,命名局部變量或方法參數 _1
、 _2
等會導致警告。
_1 = 'test' # warning: `_1' is reserved as numbered parameter
使用隱式編號參數會影響塊的數量:
p = proc { _1 + _2 }
l = lambda { _1 + _2 }
p.parameters # => [[:opt, :_1], [:opt, :_2]]
p.arity # => 2
l.parameters # => [[:req, :_1], [:req, :_2]]
l.arity # => 2
帶編號參數的塊不能嵌套:
%w[test me].each { _1.each_char { p _1 } } # SyntaxError (numbered parameter is already used in outer block here) # %w[test me].each { _1.each_char { p _1 } } # ^~
編號參數是在 Ruby 2.7 中引入的。
相關用法
- Ruby Process.groups用法及代碼示例
- Ruby Process.wait2用法及代碼示例
- Ruby Process.getpgrp用法及代碼示例
- Ruby Proc.eql?用法及代碼示例
- Ruby Process.setproctitle用法及代碼示例
- Ruby Process.setrlimit用法及代碼示例
- Ruby Proc.prc ==用法及代碼示例
- Ruby Process.uid用法及代碼示例
- Ruby Process.pid用法及代碼示例
- Ruby Proc.ruby2_keywords用法及代碼示例
- Ruby Proc.new用法及代碼示例
- Ruby Process.detach用法及代碼示例
- Ruby Process.maxgroups用法及代碼示例
- Ruby Process.clock_gettime用法及代碼示例
- Ruby Proc.lambda?用法及代碼示例
- Ruby Proc.arity用法及代碼示例
- Ruby Process.exec用法及代碼示例
- Ruby Process.groups=用法及代碼示例
- Ruby Proc.(params,...)用法及代碼示例
- Ruby Proc.curry用法及代碼示例
- Ruby Process.clock_getres用法及代碼示例
- Ruby Proc.prc << g用法及代碼示例
- Ruby Process.getsid用法及代碼示例
- Ruby Process.getpriority用法及代碼示例
- Ruby Process.times用法及代碼示例
注:本文由純淨天空篩選整理自ruby-lang.org大神的英文原創作品 Proc類。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。