класс 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 } -
Создайте proc с семантикой лямбда-функции, используя метод
Kernel#lambda(подробнее о лямбда-функциях см. ниже):lambda1 = lambda {|x| x**2 } -
Используйте синтаксис литерала лямбда-функции (также создает proc с семантикой лямбда-функции):
lambda2 = ->(x) { x**2 }
Семантика лямбда-функций и обычных proc
Proc бывают двух типов: лямбда-функции и обычные proc. Различия:
-
В лямбда-функциях
returnиbreakозначают выход из этой лямбда-функции; -
В обычных proc
returnозначает выход из охватывающего метода (и выброситLocalJumpErrorпри вызове вне метода); -
В обычных proc
breakозначает выход из метода, для которого был предоставлен блок (и выброситLocalJumpErrorесли вызван после возвращения метода); -
В лямбда-функциях аргументы обрабатываются так же, как и в методах: строго, с
ArgumentErrorпри несовпадении количества аргументов и без дополнительной обработки аргументов; -
Обычные proc принимают аргументы более гибко: отсутствующие аргументы заполняются
nil, одиночный аргумент типаArrayдеконструируется, если у proc несколько аргументов, и не возникает ошибок при наличии дополнительных аргументов.
Примеры:
# +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
Лямбда-функции полезны как автономные функции, в особенности как аргументы высших функций, ведя себя точно как методы Ruby.
Proc полезны для реализации итераторов:
def test
[[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
end
Внутри map, блок кода обрабатывается как обычная (не лямбда-функция) proc, что означает, что внутренние массивы будут деконструированы в пары аргументов, а return выйдет из метода test. Это невозможно с более строгой лямбда-функцией.
Чтобы отличить лямбда-функцию от обычной 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)
Исключением является динамическое определение метода: даже если он определен с помощью передачи обычной 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 как обычную proc, но определяет метод с нормальной семантикой.
Преобразование других объектов в 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 выходят из блока непосредственно в лямбда-функциях, лямбда-функции не могут быть осиротевшими.
Параметры с номерами
Параметры с номерами — это неявно определенные параметры блока, предназначенные для упрощения написания коротких блоков:
# 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.
Методы открытого класса
static VALUE
rb_proc_s_new(int argc, VALUE *argv, VALUE klass)
{
VALUE block = proc_new(klass, FALSE, FALSE);
rb_obj_call_init_kw(block, argc, argv, RB_PASS_CALLED_KEYWORDS);
return block;
} Создает новый объект Proc, привязанный к текущему контексту. Proc::new может вызываться без блока только внутри метода с присоединенным блоком, в этом случае этот блок преобразуется в объект Proc.
def proc_from
Proc.new
end
proc = proc_from { "hello" }
proc.call #=> "hello"
Методы открытого экземпляра
static VALUE
proc_compose_to_left(VALUE self, VALUE g)
{
return rb_proc_compose_to_left(self, to_callable(g));
} Возвращает процедуру, которая является композицией этой процедуры и заданного g. Возвращаемая процедура принимает переменное число аргументов, вызывает g с ними, а затем вызывает эту процедуру с результатом.
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.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;
} Две процедуры идентичны только в том случае, если они были созданы из одного и того же блока кода.
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() с заданными параметрами. Это синтаксический сахар для скрытия «вызова».
Для процедур, созданных с помощью lambda или ->(), генерируется ошибка, если передано неправильное количество параметров. Для процедур, созданных с помощью Proc.new или Kernel.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?.
static VALUE
proc_compose_to_right(VALUE self, VALUE g)
{
return rb_proc_compose_to_right(self, to_callable(g));
} Возвращает процедуру, которая является композицией этой процедуры и заданного g. Возвращаемая процедура принимает переменное число аргументов, вызывает эту процедуру с ними, а затем вызывает 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() с заданными параметрами. Это синтаксический сахар для скрытия «вызова».
Для процедур, созданных с помощью lambda или ->(), генерируется ошибка, если передано неправильное количество параметров. Для процедур, созданных с помощью Proc.new или Kernel.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?.
static VALUE
proc_arity(VALUE self)
{
int arity = rb_proc_arity(self);
return INT2FIX(arity);
} Возвращает количество обязательных аргументов. Если блок объявлен как принимающий ноль аргументов, возвращает 0. Если блок известен как принимающий ровно n аргументов, возвращает n. Если у блока есть необязательные аргументы, возвращает -n-1, где n — количество обязательных аргументов, за исключением блоков, которые не являются лямбдами и имеют только конечное число необязательных аргументов; в последнем случае возвращает 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(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->location.pathobj);
bind->first_lineno = FIX2INT(rb_iseq_first_lineno(iseq));
}
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
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() с заданными параметрами. Это синтаксический сахар для скрытия «вызова».
Для процедур, созданных с помощью lambda или ->(), генерируется ошибка, если передано неправильное количество параметров. Для процедур, созданных с помощью Proc.new или Kernel.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?.
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);
} Возвращает процедуру в виде «замыкания». Если задан необязательный аргумент arity, он определяет количество аргументов. Процедура в виде «замыкания» получает некоторые аргументы. Если предоставлено достаточное количество аргументов, оно передает эти аргументы исходной процедуре и возвращает результат. В противном случае возвращает другую процедуру в виде «замыкания», которая принимает оставшиеся аргументы.
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
Две процедуры идентичны только в том случае, если они были созданы из одного и того же блока кода.
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);
} Возвращает значение хэша, соответствующее телу процедуры.
См. также Object#hash.
Возвращает уникальный идентификатор этой процедуры, а также указание, где она была определена.
VALUE
rb_proc_lambda_p(VALUE procval)
{
rb_proc_t *proc;
GetProcPtr(procval, proc);
return proc->is_lambda ? Qtrue : Qfalse;
} Возвращает true, если объект Proc является лямбда-функцией. Возвращает false, если это не лямбда-функция.
Свойство лямбда-функции влияет на обработку аргументов и поведение return и break.
Объект Proc, сгенерированный с помощью proc, игнорирует дополнительные аргументы.
proc {|a,b| [a,b] }.call(1,2,3) #=> [1,2]
Он предоставляет значения по умолчанию для отсутствующих аргументов.
proc {|a,b| [a,b] }.call(1) #=> [1,nil]
Он расширяет массив, переданный в качестве единственного аргумента.
proc {|a,b| [a,b] }.call([1,2]) #=> [1,2]
Объект Proc, сгенерированный с помощью lambda, не имеет таких функций.
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? — предикат, проверяющий наличие этих функций. Он возвращает false, если такие функции не применяются.
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, который не является лямбда-функцией. Это единственное исключение, когда особенности не сохраняются.
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(VALUE self)
{
int is_proc;
const rb_iseq_t *iseq = rb_proc_get_iseq(self, &is_proc);
if (!iseq) {
return rb_unnamed_parameters(rb_proc_arity(self));
}
return rb_iseq_parameters(iseq, is_proc);
} Возвращает информацию об параметрах данного proc.
prc = lambda{|x, y=42, *other|}
prc.parameters #=> [[:req, :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 (proc->block.as.captured.code.iseq->body->param.flags.has_rest &&
!proc->block.as.captured.code.iseq->body->param.flags.has_kw &&
!proc->block.as.captured.code.iseq->body->param.flags.has_kwrest) {
proc->block.as.captured.code.iseq->body->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. Это должно вызываться только для proc, которые принимают оператор splat (*args) но не явные ключевые слова или оператор splat для ключевых слов. Это помечает proc таким образом, что если proc вызывается с ключевыми аргументами, то конечный хэш-аргумент помечается специальным флагом, таким образом, если он является последним элементом обычного оператора splat в другом вызове метода, и этот вызов метода не включает явные ключевые слова или оператор splat для ключевых слов, то последний элемент интерпретируется как ключевые слова. Другими словами, ключевые слова будут переданы через proc в другие методы.
Это должно использоваться только для proc, которые делегируют ключевые слова другому методу, и только для обратной совместимости с версиями 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
VALUE
rb_proc_location(VALUE self)
{
return iseq_location(rb_proc_get_iseq(self, 0));
} Возвращает имя файла и номер строки в Ruby исходном коде, содержащем этот proc, или nil, если этот proc не был определен в Ruby (т.е. нативный).
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() с заданными параметрами. Это синтаксический сахар, скрывающий «вызов».
Для proc, созданных с помощью lambda или ->(), генерируется ошибка, если передано неверное количество параметров. Для proc, созданных с помощью Proc.new или Kernel.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?.
Ruby Core © 1993–2020 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.