class 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 }
-
将代码块接收到 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 字面量 语法(也构造一个具有 lambda 语义的 proc)
lambda2 = ->(x) { x**2 }
Lambda 和非 Lambda 语义¶ ↑
Procs 有两种类型:lambda 和非 lambda(常规 procs)。区别在于
-
在 lambdas 中,
return和break表示从该 lambda 中退出; -
在非 lambda procs 中,
return表示从包含它的方法中退出(如果在方法外部调用,将抛出LocalJumpError); -
在非 lambda procs 中,
break表示从给定代码块的方法中退出。(如果在方法返回后调用,将抛出LocalJumpError); -
在 lambdas 中,参数的处理方式与方法相同:严格,对于参数数量不匹配会抛出
ArgumentError,并且没有额外的参数处理; -
常规 procs 更宽松地接受参数:缺少的参数会用
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
Lambdas 作为自给自足的函数非常有用,特别适用于作为高阶函数的参数,其行为与 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
由于 return 和 break 在 lambdas 中会退出代码块本身,因此 lambdas 不可能是孤立的。
匿名块参数¶ ↑
为了简化编写短代码块,Ruby 提供了两种不同类型的匿名参数:it(单个参数)和编号参数:_1、_2 等。
# 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] # it: %w[test me please].each { puts it.upcase } # prints TEST, ME, PLEASE (1..5).map { it**2 } # => [1, 4, 9, 16, 25] # Numbered parameter: %w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE (1..5).map { _1**2 } # => [1, 4, 9, 16, 25]
it¶ ↑
当没有定义显式参数时,it 是一个在代码块内部可用的名称,如上所示。
%w[test me please].each { puts it.upcase } # prints TEST, ME, PLEASE (1..5).map { it**2 } # => [1, 4, 9, 16, 25]
it 是一个“软关键字”:它不是保留名称,可以用作方法和局部变量的名称
it = 5 # no warnings def it(&block) # RSpec-like API, no warnings # ... end
即使在将其用作隐式参数的代码块中,也可以将 it 用作局部变量(尽管这种风格显然会令人困惑)
[1, 2, 3].each { # takes a value of implicit parameter "it" and uses it to # define a local variable with the same name it = it**2 p it }
在定义了显式参数的代码块中使用 it 会引发异常
[1, 2, 3].each { |x| p it }
# syntax error found (SyntaxError)
# [1, 2, 3].each { |x| p it }
# ^~ `it` is not allowed when an ordinary parameter is defined
但是,如果可以使用局部名称(变量或方法),则将使用该名称
it = 5 [1, 2, 3].each { |x| p it } # Prints 5, 5, 5
可以使用 it 的代码块可以嵌套
%w[test me].each { it.each_char { p it } } # Prints "t", "e", "s", "t", "m", "e"
使用 it 的代码块被认为有一个参数
p = proc { it**2 } l = lambda { it**2 } p.parameters # => [[:opt, nil]] p.arity # => 1 l.parameters # => [[:req]] l.arity # => 1
编号参数¶ ↑
编号参数是隐式命名块参数的另一种方法。与 it 不同,编号参数允许在一个代码块中引用多个参数。
%w[test me please].each { puts _1.upcase } # prints TEST, ME, PLEASE {a: 100, b: 200}.map { "#{_1} = #{_2}" } # => "a = 100", "b = 200"
支持从 _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)
编号参数也不能与 it 混合使用
[10, 20, 30].map { _1 + it }
# SyntaxError: `it` is not allowed when a numbered parameter is already used
为避免冲突,将局部变量或方法参数命名为 _1、_2 等会导致错误。
_1 = 'test' # ^~ _1 is reserved for numbered parameters (SyntaxError)
使用隐式编号参数会影响代码块的 arity
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 } }
# numbered parameter is already used in outer block (SyntaxError)
# %w[test me].each { _1.each_char { p _1 } }
# ^~
公共类方法
源代码
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 = Proc.new { "hello" } proc.call #=> "hello"
如果没有代码块调用,则引发 ArgumentError。
Proc.new #=> ArgumentError
公共实例方法
源代码
static VALUE
proc_compose_to_left(VALUE self, VALUE g)
{
return rb_proc_compose_to_left(self, to_callable(g));
}
返回一个 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_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.code.ifunc != \
other_block->as.captured.code.ifunc) {
return Qfalse;
}
if (memcmp(
((cfunc_proc_t *)self_proc)->env,
((cfunc_proc_t *)other_proc)->env,
sizeof(((cfunc_proc_t *)self_proc)->env))) {
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;
}
当且仅当两个 procs 是从同一代码块创建时,它们才相同。
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
调用代码块,使用类似于方法调用语义的方式将代码块的参数设置为 _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()。这是一个隐藏“call”的语法糖。
对于使用 lambda 或 ->() 创建的 procs,如果传递给 proc 的参数数量错误,则会生成错误。对于使用 Proc.new 或 Kernel.proc 创建的 procs,额外的参数会被默默丢弃,缺少的参数会被设置为 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?。
源代码
static VALUE
proc_compose_to_right(VALUE self, VALUE g)
{
return rb_proc_compose_to_right(self, to_callable(g));
}
返回一个 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')
调用代码块,使用类似于方法调用语义的方式将代码块的参数设置为 _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()。这是一个隐藏“call”的语法糖。
对于使用 lambda 或 ->() 创建的 procs,如果传递给 proc 的参数数量错误,则会生成错误。对于使用 Proc.new 或 Kernel.proc 创建的 procs,额外的参数会被默默丢弃,缺少的参数会被设置为 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?。
源代码
static VALUE
proc_arity(VALUE self)
{
int arity = rb_proc_arity(self);
return INT2FIX(arity);
}
返回强制参数的数量。如果该代码块被声明为不接受任何参数,则返回 0。如果已知该代码块恰好接受 n 个参数,则返回 n。如果该代码块有可选参数,则返回 -n-1,其中 n 是强制参数的数量,但对于不是 lambdas 并且仅具有有限数量可选参数的代码块除外;在后一种情况下,返回 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_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(Qnil, 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;
}
返回与 _prc_ 关联的绑定。
def fred(param) proc {} end b = fred(99) eval("param", b.binding) #=> 99
源代码
0
static VALUE
proc_call(int argc, VALUE *argv, VALUE procval)
{
/* removed */
}
调用代码块,使用类似于方法调用语义的方式将代码块的参数设置为 _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()。这是一个隐藏“call”的语法糖。
对于使用 lambda 或 ->() 创建的 procs,如果传递给 proc 的参数数量错误,则会生成错误。对于使用 Proc.new 或 Kernel.proc 创建的 procs,额外的参数会被默默丢弃,缺少的参数会被设置为 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?。
源代码
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。如果给定了可选的 _arity_ 参数,它将确定参数的数量。柯里化的 proc 接收一些参数。如果提供了足够数量的参数,它会将提供的参数传递给原始 proc 并返回结果。否则,返回另一个柯里化的 proc,该 proc 接受其余的参数。
当对具有可变参数的 procs 进行柯里化时,应提供可选的 _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
当且仅当两个 procs 是从同一代码块创建时,它们才相同。
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_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 主体对应的哈希值。
另请参见 Object#hash。
源代码
VALUE
rb_proc_lambda_p(VALUE procval)
{
rb_proc_t *proc;
GetProcPtr(procval, proc);
return RBOOL(proc->is_lambda);
}
如果 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 总是定义一个没有特性的方法,即使给定的 Proc 对象不是 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 定义了一个没有特性的方法。
源代码
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);
}
返回此 proc 的参数信息。如果提供了 lambda 关键字且不为 nil,则当其为 true 时将 proc 视为 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
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 标记为通过普通参数 splat 传递关键字。此方法应仅在接受参数 splat(*args)但不接受显式关键字或关键字 splat 的 proc 上调用。它标记 proc,使其在被调用时带有关键字参数,则最后一个哈希参数会被标记一个特殊标志,使得如果它是另一个方法调用的普通参数 splat 的最后一个元素,且该方法调用不包含显式关键字或关键字 splat,则最后一个元素会被解释为关键字。换句话说,关键字将通过 proc 传递给其他方法。
此方法仅应在 proc 将关键字委托给另一个方法时使用,并且仅用于与 2.7 之前的 Ruby 版本向后兼容。
此方法可能会在某个时候被移除,因为它仅用于向后兼容。由于在 2.7 之前的 Ruby 版本中不存在此方法,请在调用它之前检查 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
源代码
VALUE
rb_proc_location(VALUE self)
{
return iseq_location(rb_proc_get_iseq(self, 0));
}
返回包含此 proc 的 Ruby 源文件名和行号,如果此 proc 不是在 Ruby 中定义的(即,本地的),则返回 nil。
源代码
源代码
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);
}
返回此 proc 的唯一标识符,以及 proc 的定义位置。
调用代码块,使用类似于方法调用语义的方式将代码块的参数设置为 _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()。这是一个隐藏“call”的语法糖。
对于使用 lambda 或 ->() 创建的 procs,如果传递给 proc 的参数数量错误,则会生成错误。对于使用 Proc.new 或 Kernel.proc 创建的 procs,额外的参数会被默默丢弃,缺少的参数会被设置为 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?。