類別 Proc
Proc
物件是對程式碼區塊的封裝,可以儲存在區域變數中,傳遞給方法或另一個 Proc
,並且可以呼叫。 Proc
是 Ruby 中的基本概念,也是其函式程式設計功能的核心。
square = Proc.new {|x| x**2 } square.call(3) #=> 9 # shorthands: square.(3) #=> 9 square[3] #=> 9
Proc
物件是封閉,表示它們會記住並可以使用建立它們時所處的整個環境。
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 }
-
將程式碼區塊接收為程序引數(請注意
&
)def make_proc(&block) block end proc3 = make_proc {|x| x**2 }
-
使用
Kernel#lambda
方法建立具有 lambda 語意的程序(請參閱下方有關 lambda 的說明)lambda1 = lambda {|x| x**2 }
-
使用 Lambda 程序文字 語法(也會建立具有 lambda 語意的程序)
lambda2 = ->(x) { x**2 }
Lambda 和非 lambda 語意¶ ↑
程序有兩種形式:lambda 和非 lambda(一般程序)。差異如下
-
在 lambda 中,
return
和break
表示退出此 lambda; -
在非 lambda 程序中,
return
表示退出包含的方法(如果在方法外呼叫,將擲回LocalJumpError
); -
在非 lambda 程序中,
break
表示退出給定區塊的方法。(如果在方法傳回後呼叫,將擲回LocalJumpError
); -
在 lambda 中,參數的處理方式與方法中相同:嚴格,如果參數數量不符,則會發生
ArgumentError
,而且不會進行額外的參數處理; -
一般程序更寬鬆地接受參數:缺少的參數會填入
nil
,如果程序有多個參數,則會解構單一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 作為自給自足的函式很有用,特別是作為高階函式的參數時,其行為與 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 和一般 proc。
Lambda 語意通常會在 proc 的生命週期中保留,包括解構為程式碼區塊的 &
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 proc 來定義,方法仍具有檢查參數的正常語意。
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,但定義的方法具有正常的語意。
將其他物件轉換為 procs¶ ↑
任何實作 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
由於 lambda 中的 return
和 break
會退出區塊本身,因此 lambda 無法孤立。
編號參數¶ ↑
編號參數是隱含定義的區塊參數,旨在簡化撰寫簡短區塊
# 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
,以及單行區塊。
編號參數無法與明確命名的參數一起使用
[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 中引入。
公開類別方法
建立新的 Proc
物件,繫結到目前的內容。
proc = Proc.new { "hello" } proc.call #=> "hello"
若未呼叫區塊,則會引發 ArgumentError
。
Proc.new #=> ArgumentError
static VALUE rb_proc_s_new(int argc, VALUE *argv, VALUE klass) { VALUE block = proc_new(klass, FALSE); rb_obj_call_init_kw(block, argc, argv, RB_PASS_CALLED_KEYWORDS); return block; }
公開實例方法
傳回一個 proc,它是此 proc 與給定的 g 的組合。傳回的 proc 會接收可變數目的參數,使用這些參數呼叫 g,然後使用結果呼叫此 proc。
f = proc {|x| x * x } g = proc {|x| x + x } p (f << g).call(2) #=> 16
請參閱 Proc#>>
以取得詳細說明。
static VALUE proc_compose_to_left(VALUE self, VALUE g) { return rb_proc_compose_to_left(self, to_callable(g)); }
只有當兩個 proc 從相同的程式碼區塊建立時,它們才會相同。
def return_block(&block) block end def pass_block_twice(&block) [return_block(&block), return_block(&block)] end block1, block2 = pass_block_twice { puts 'test' } # Blocks might be instantiated into Proc's lazily, so they may, or may not, # be the same object. # But they are produced from the same code block, so they are equal block1 == block2 #=> true # Another Proc will never be equal, even if the code is the "same" block1 == proc { puts 'test' } #=> false
static VALUE proc_eq(VALUE self, VALUE other) { const rb_proc_t *self_proc, *other_proc; const struct rb_block *self_block, *other_block; if (rb_obj_class(self) != rb_obj_class(other)) { return Qfalse; } GetProcPtr(self, self_proc); GetProcPtr(other, other_proc); if (self_proc->is_from_method != other_proc->is_from_method || self_proc->is_lambda != other_proc->is_lambda) { return Qfalse; } self_block = &self_proc->block; other_block = &other_proc->block; if (vm_block_type(self_block) != vm_block_type(other_block)) { return Qfalse; } switch (vm_block_type(self_block)) { case block_type_iseq: if (self_block->as.captured.ep != \ other_block->as.captured.ep || self_block->as.captured.code.iseq != \ other_block->as.captured.code.iseq) { return Qfalse; } break; case block_type_ifunc: if (self_block->as.captured.ep != \ other_block->as.captured.ep || self_block->as.captured.code.ifunc != \ other_block->as.captured.code.ifunc) { return Qfalse; } break; case block_type_proc: if (self_block->as.proc != other_block->as.proc) { return Qfalse; } break; case block_type_symbol: if (self_block->as.symbol != other_block->as.symbol) { return Qfalse; } break; } return Qtrue; }
呼叫區塊,使用接近方法呼叫語意的 params 中的值設定區塊的參數。傳回在區塊中評估的最後一個表達式的值。
a_proc = Proc.new {|scalar, *values| values.map {|value| value*scalar } } a_proc.call(9, 1, 2, 3) #=> [9, 18, 27] a_proc[9, 1, 2, 3] #=> [9, 18, 27] a_proc.(9, 1, 2, 3) #=> [9, 18, 27] a_proc.yield(9, 1, 2, 3) #=> [9, 18, 27]
請注意,prc.()
會使用給定的參數呼叫 prc.call()
。這是隱藏「呼叫」的語法糖。
對於使用 lambda
或 ->()
建立的 proc,如果傳遞給 proc 的參數數目不正確,則會產生錯誤。對於使用 Proc.new
或 Kernel.proc
建立的 proc,額外的參數會靜默捨棄,而遺失的參數會設定為 nil
。
a_proc = proc {|a,b| [a,b] } a_proc.call(1) #=> [1, nil] a_proc = lambda {|a,b| [a,b] } a_proc.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
另請參閱 Proc#lambda?
。
傳回一個 proc,它是此 proc 與給定的 g 的組合。傳回的 proc 會接收可變數目的參數,使用這些參數呼叫此 proc,然後使用結果呼叫 g。
f = proc {|x| x * x } g = proc {|x| x + x } p (f >> g).call(2) #=> 8
g 可以是其他 Proc
或 Method
,或任何其他回應 call
方法的物件
class Parser def self.call(text) # ...some complicated parsing logic... end end pipeline = File.method(:read) >> Parser >> proc { |data| puts "data size: #{data.count}" } pipeline.call('data.json')
static VALUE proc_compose_to_right(VALUE self, VALUE g) { return rb_proc_compose_to_right(self, to_callable(g)); }
呼叫區塊,使用接近方法呼叫語意的 params 中的值設定區塊的參數。傳回在區塊中評估的最後一個表達式的值。
a_proc = Proc.new {|scalar, *values| values.map {|value| value*scalar } } a_proc.call(9, 1, 2, 3) #=> [9, 18, 27] a_proc[9, 1, 2, 3] #=> [9, 18, 27] a_proc.(9, 1, 2, 3) #=> [9, 18, 27] a_proc.yield(9, 1, 2, 3) #=> [9, 18, 27]
請注意,prc.()
會使用給定的參數呼叫 prc.call()
。這是隱藏「呼叫」的語法糖。
對於使用 lambda
或 ->()
建立的 proc,如果傳遞給 proc 的參數數目不正確,則會產生錯誤。對於使用 Proc.new
或 Kernel.proc
建立的 proc,額外的參數會靜默捨棄,而遺失的參數會設定為 nil
。
a_proc = proc {|a,b| [a,b] } a_proc.call(1) #=> [1, nil] a_proc = lambda {|a,b| [a,b] } a_proc.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
另請參閱 Proc#lambda?
。
傳回強制參數的數量。如果宣告區塊不帶任何參數,則傳回 0。如果已知區塊會帶有正好 n 個參數,則傳回 n。如果區塊有選用參數,則傳回 -n-1,其中 n 是強制參數的數量,但非 lambda 且只有有限個選用參數的區塊除外;在後一種情況下,傳回 n。關鍵字參數將視為單一額外參數,如果任何關鍵字參數為強制參數,則該參數為強制參數。proc
如果沒有參數宣告,則與宣告 ||
為其參數的區塊相同。
proc {}.arity #=> 0 proc { || }.arity #=> 0 proc { |a| }.arity #=> 1 proc { |a, b| }.arity #=> 2 proc { |a, b, c| }.arity #=> 3 proc { |*a| }.arity #=> -1 proc { |a, *b| }.arity #=> -2 proc { |a, *b, c| }.arity #=> -3 proc { |x:, y:, z:0| }.arity #=> 1 proc { |*a, x:, y:0| }.arity #=> -2 proc { |a=0| }.arity #=> 0 lambda { |a=0| }.arity #=> -1 proc { |a=0, b| }.arity #=> 1 lambda { |a=0, b| }.arity #=> -2 proc { |a=0, b=0| }.arity #=> 0 lambda { |a=0, b=0| }.arity #=> -1 proc { |a, b=0| }.arity #=> 1 lambda { |a, b=0| }.arity #=> -2 proc { |(a, b), c=0| }.arity #=> 1 lambda { |(a, b), c=0| }.arity #=> -2 proc { |a, x:0, y:0| }.arity #=> 1 lambda { |a, x:0, y:0| }.arity #=> -2
static VALUE proc_arity(VALUE self) { int arity = rb_proc_arity(self); return INT2FIX(arity); }
傳回與 prc 相關聯的繫結。
def fred(param) proc {} end b = fred(99) eval("param", b.binding) #=> 99
static VALUE proc_binding(VALUE self) { VALUE bindval, binding_self = Qundef; rb_binding_t *bind; const rb_proc_t *proc; const rb_iseq_t *iseq = NULL; const struct rb_block *block; const rb_env_t *env = NULL; GetProcPtr(self, proc); block = &proc->block; if (proc->is_isolated) rb_raise(rb_eArgError, "Can't create Binding from isolated Proc"); again: switch (vm_block_type(block)) { case block_type_iseq: iseq = block->as.captured.code.iseq; binding_self = block->as.captured.self; env = VM_ENV_ENVVAL_PTR(block->as.captured.ep); break; case block_type_proc: GetProcPtr(block->as.proc, proc); block = &proc->block; goto again; case block_type_ifunc: { const struct vm_ifunc *ifunc = block->as.captured.code.ifunc; if (IS_METHOD_PROC_IFUNC(ifunc)) { VALUE method = (VALUE)ifunc->data; VALUE name = rb_fstring_lit("<empty_iseq>"); rb_iseq_t *empty; binding_self = method_receiver(method); iseq = rb_method_iseq(method); env = VM_ENV_ENVVAL_PTR(block->as.captured.ep); env = env_clone(env, method_cref(method)); /* set empty iseq */ empty = rb_iseq_new(NULL, name, name, Qnil, 0, ISEQ_TYPE_TOP); RB_OBJ_WRITE(env, &env->iseq, empty); break; } } /* FALLTHROUGH */ case block_type_symbol: rb_raise(rb_eArgError, "Can't create Binding from C level Proc"); UNREACHABLE_RETURN(Qnil); } bindval = rb_binding_alloc(rb_cBinding); GetBindingPtr(bindval, bind); RB_OBJ_WRITE(bindval, &bind->block.as.captured.self, binding_self); RB_OBJ_WRITE(bindval, &bind->block.as.captured.code.iseq, env->iseq); rb_vm_block_ep_update(bindval, &bind->block, env->ep); RB_OBJ_WRITTEN(bindval, Qundef, VM_ENV_ENVVAL(env->ep)); if (iseq) { rb_iseq_check(iseq); RB_OBJ_WRITE(bindval, &bind->pathobj, ISEQ_BODY(iseq)->location.pathobj); bind->first_lineno = ISEQ_BODY(iseq)->location.first_lineno; } else { RB_OBJ_WRITE(bindval, &bind->pathobj, rb_iseq_pathobj_new(rb_fstring_lit("(binding)"), Qnil)); bind->first_lineno = 1; } return bindval; }
呼叫區塊,使用接近方法呼叫語意的 params 中的值設定區塊的參數。傳回在區塊中評估的最後一個表達式的值。
a_proc = Proc.new {|scalar, *values| values.map {|value| value*scalar } } a_proc.call(9, 1, 2, 3) #=> [9, 18, 27] a_proc[9, 1, 2, 3] #=> [9, 18, 27] a_proc.(9, 1, 2, 3) #=> [9, 18, 27] a_proc.yield(9, 1, 2, 3) #=> [9, 18, 27]
請注意,prc.()
會使用給定的參數呼叫 prc.call()
。這是隱藏「呼叫」的語法糖。
對於使用 lambda
或 ->()
建立的 proc,如果傳遞給 proc 的參數數目不正確,則會產生錯誤。對於使用 Proc.new
或 Kernel.proc
建立的 proc,額外的參數會靜默捨棄,而遺失的參數會設定為 nil
。
a_proc = proc {|a,b| [a,b] } a_proc.call(1) #=> [1, nil] a_proc = lambda {|a,b| [a,b] } a_proc.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
另請參閱 Proc#lambda?
。
0 static VALUE proc_call(int argc, VALUE *argv, VALUE procval) { /* removed */ }
傳回已柯里化的 proc。如果提供選用的 arity 參數,則它會決定參數的數量。已柯里化的 proc 會接收一些參數。如果提供足夠數量的參數,則它會將提供的參數傳遞給原始 proc 並傳回結果。否則,傳回另一個已柯里化的 proc,它會採用其餘參數。
在柯里化帶有變數參數的 proc 時,應提供選用的 arity 參數,以決定在呼叫 proc 之前需要多少個參數。
b = proc {|x, y, z| (x||0) + (y||0) + (z||0) } p b.curry[1][2][3] #=> 6 p b.curry[1, 2][3, 4] #=> 6 p b.curry(5)[1][2][3][4][5] #=> 6 p b.curry(5)[1, 2][3, 4][5] #=> 6 p b.curry(1)[1] #=> 1 b = proc {|x, y, z, *w| (x||0) + (y||0) + (z||0) + w.inject(0, &:+) } p b.curry[1][2][3] #=> 6 p b.curry[1, 2][3, 4] #=> 10 p b.curry(5)[1][2][3][4][5] #=> 15 p b.curry(5)[1, 2][3, 4][5] #=> 15 p b.curry(1)[1] #=> 1 b = lambda {|x, y, z| (x||0) + (y||0) + (z||0) } p b.curry[1][2][3] #=> 6 p b.curry[1, 2][3, 4] #=> wrong number of arguments (given 4, expected 3) p b.curry(5) #=> wrong number of arguments (given 5, expected 3) p b.curry(1) #=> wrong number of arguments (given 1, expected 3) b = lambda {|x, y, z, *w| (x||0) + (y||0) + (z||0) + w.inject(0, &:+) } p b.curry[1][2][3] #=> 6 p b.curry[1, 2][3, 4] #=> 10 p b.curry(5)[1][2][3][4][5] #=> 15 p b.curry(5)[1, 2][3, 4][5] #=> 15 p b.curry(1) #=> wrong number of arguments (given 1, expected 3) b = proc { :foo } p b.curry[] #=> :foo
static VALUE proc_curry(int argc, const VALUE *argv, VALUE self) { int sarity, max_arity, min_arity = rb_proc_min_max_arity(self, &max_arity); VALUE arity; if (rb_check_arity(argc, 0, 1) == 0 || NIL_P(arity = argv[0])) { arity = INT2FIX(min_arity); } else { sarity = FIX2INT(arity); if (rb_proc_lambda_p(self)) { rb_check_arity(sarity, min_arity, max_arity); } } return make_curry_proc(self, rb_ary_new(), arity); }
只有當兩個 proc 從相同的程式碼區塊建立時,它們才會相同。
def return_block(&block) block end def pass_block_twice(&block) [return_block(&block), return_block(&block)] end block1, block2 = pass_block_twice { puts 'test' } # Blocks might be instantiated into Proc's lazily, so they may, or may not, # be the same object. # But they are produced from the same code block, so they are equal block1 == block2 #=> true # Another Proc will never be equal, even if the code is the "same" block1 == proc { puts 'test' } #=> false
傳回與 proc 主體相符的雜湊值。
另請參閱 Object#hash
。
static VALUE proc_hash(VALUE self) { st_index_t hash; hash = rb_hash_start(0); hash = rb_hash_proc(hash, self); hash = rb_hash_end(hash); return ST2FIX(hash); }
如果 Proc
物件是 lambda,則傳回 true
。如果是非 lambda,則傳回 false
。
lambda 特性會影響引數處理以及 return
和 break
的行為。
由 proc
產生的 Proc
物件會忽略額外的引數。
proc {|a,b| [a,b] }.call(1,2,3) #=> [1,2]
它會為遺失的引數提供 nil
。
proc {|a,b| [a,b] }.call(1) #=> [1,nil]
它會展開單一陣列引數。
proc {|a,b| [a,b] }.call([1,2]) #=> [1,2]
由 lambda
產生的 Proc
物件沒有這些技巧。
lambda {|a,b| [a,b] }.call(1,2,3) #=> ArgumentError lambda {|a,b| [a,b] }.call(1) #=> ArgumentError lambda {|a,b| [a,b] }.call([1,2]) #=> ArgumentError
Proc#lambda?
是這些技巧的謂詞。如果沒有套用任何技巧,則傳回 true
。
lambda {}.lambda? #=> true proc {}.lambda? #=> false
Proc.new
與 proc
相同。
Proc.new {}.lambda? #=> false
lambda
、proc
和 Proc.new
會保留由 &
引數提供的 Proc
物件的技巧。
lambda(&lambda {}).lambda? #=> true proc(&lambda {}).lambda? #=> true Proc.new(&lambda {}).lambda? #=> true lambda(&proc {}).lambda? #=> false proc(&proc {}).lambda? #=> false Proc.new(&proc {}).lambda? #=> false
由 &
引數產生的 Proc
物件具有技巧
def n(&b) b.lambda? end n {} #=> false
如果由 &
引數提供 Proc
物件,則 &
引數會保留這些技巧。
n(&lambda {}) #=> true n(&proc {}) #=> false n(&Proc.new {}) #=> false
從方法轉換而來的 Proc
物件沒有技巧。
def m() end method(:m).to_proc.lambda? #=> true n(&method(:m)) #=> true n(&method(:m).to_proc) #=> true
define_method
與方法定義的處理方式相同。已定義的方法沒有技巧。
class C define_method(:d) {} end C.new.d(1,2) #=> ArgumentError C.new.method(:d).to_proc.lambda? #=> true
define_method
永遠會定義沒有技巧的方法,即使提供了非 lambda 的 Proc
物件。這是技巧不會保留的唯一例外。
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 會定義沒有技巧的方法。
VALUE rb_proc_lambda_p(VALUE procval) { rb_proc_t *proc; GetProcPtr(procval, proc); return RBOOL(proc->is_lambda); }
傳回此程序的參數資訊。如果提供了 lambda 關鍵字且不為 nil,則在為 true 時將程序視為 lambda,在為 false 時將程序視為非 lambda。
prc = proc{|x, y=42, *other|} prc.parameters #=> [[:opt, :x], [:opt, :y], [:rest, :other]] prc = lambda{|x, y=42, *other|} prc.parameters #=> [[:req, :x], [:opt, :y], [:rest, :other]] prc = proc{|x, y=42, *other|} prc.parameters(lambda: true) #=> [[:req, :x], [:opt, :y], [:rest, :other]] prc = lambda{|x, y=42, *other|} prc.parameters(lambda: false) #=> [[:opt, :x], [:opt, :y], [:rest, :other]]
static VALUE rb_proc_parameters(int argc, VALUE *argv, VALUE self) { static ID keyword_ids[1]; VALUE opt, lambda; VALUE kwargs[1]; int is_proc ; const rb_iseq_t *iseq; iseq = rb_proc_get_iseq(self, &is_proc); if (!keyword_ids[0]) { CONST_ID(keyword_ids[0], "lambda"); } rb_scan_args(argc, argv, "0:", &opt); if (!NIL_P(opt)) { rb_get_kwargs(opt, keyword_ids, 0, 1, kwargs); lambda = kwargs[0]; if (!NIL_P(lambda)) { is_proc = !RTEST(lambda); } } if (!iseq) { return rb_unnamed_parameters(rb_proc_arity(self)); } return rb_iseq_parameters(iseq, is_proc); }
標記程序為透過一般引數散佈傳遞關鍵字。這只應呼叫在接受引數散佈 (*args
) 但不接受明確關鍵字或關鍵字散佈的程序上。它會標記程序,以便如果程序以關鍵字引數呼叫,則最後一個雜湊引數會標記一個特殊旗標,以便如果它是另一個方法呼叫的最後一個一般引數散佈元素,且該方法呼叫不包含明確關鍵字或關鍵字散佈,則最後一個元素會被解釋為關鍵字。換句話說,關鍵字會透過程序傳遞到其他方法。
這只應使用在將關鍵字委派給另一個方法的程序,而且只應使用在與 Ruby 2.7 之前的版本向後相容時。
此方法可能在某個時間點移除,因為它僅存在於向後相容性中。由於它在 Ruby 2.7 之前的版本中不存在,因此在呼叫此方法之前,請檢查 proc 是否回應此方法。此外,請注意,如果移除此方法,proc 的行為將會變更,使其不會傳遞關鍵字。
module Mod foo = ->(meth, *args, &block) do send(:"do_#{meth}", *args, &block) end foo.ruby2_keywords if foo.respond_to?(:ruby2_keywords) end
static VALUE proc_ruby2_keywords(VALUE procval) { rb_proc_t *proc; GetProcPtr(procval, proc); rb_check_frozen(procval); if (proc->is_from_method) { rb_warn("Skipping set of ruby2_keywords flag for proc (proc created from method)"); return procval; } switch (proc->block.type) { case block_type_iseq: if (ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_rest && !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_kw && !ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.has_kwrest) { ISEQ_BODY(proc->block.as.captured.code.iseq)->param.flags.ruby2_keywords = 1; } else { rb_warn("Skipping set of ruby2_keywords flag for proc (proc accepts keywords or proc does not accept argument splat)"); } break; default: rb_warn("Skipping set of ruby2_keywords flag for proc (proc not defined in Ruby)"); break; } return procval; }
傳回包含此 proc 的 Ruby 原始檔檔名和行號,或如果此 proc 未在 Ruby 中定義(例如原生),則傳回 nil
。
VALUE rb_proc_location(VALUE self) { return iseq_location(rb_proc_get_iseq(self, 0)); }
傳回此 proc 的唯一識別碼,以及 proc 定義位置的指示。
static VALUE proc_to_s(VALUE self) { const rb_proc_t *proc; GetProcPtr(self, proc); return rb_block_to_s(self, &proc->block, proc->is_lambda ? " (lambda)" : NULL); }
呼叫區塊,使用接近方法呼叫語意的 params 中的值設定區塊的參數。傳回在區塊中評估的最後一個表達式的值。
a_proc = Proc.new {|scalar, *values| values.map {|value| value*scalar } } a_proc.call(9, 1, 2, 3) #=> [9, 18, 27] a_proc[9, 1, 2, 3] #=> [9, 18, 27] a_proc.(9, 1, 2, 3) #=> [9, 18, 27] a_proc.yield(9, 1, 2, 3) #=> [9, 18, 27]
請注意,prc.()
會使用給定的參數呼叫 prc.call()
。這是隱藏「呼叫」的語法糖。
對於使用 lambda
或 ->()
建立的 proc,如果傳遞給 proc 的參數數目不正確,則會產生錯誤。對於使用 Proc.new
或 Kernel.proc
建立的 proc,額外的參數會靜默捨棄,而遺失的參數會設定為 nil
。
a_proc = proc {|a,b| [a,b] } a_proc.call(1) #=> [1, nil] a_proc = lambda {|a,b| [a,b] } a_proc.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
另請參閱 Proc#lambda?
。