класс 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 (также создает proc с семантикой лямбды):
lambda2 = ->(x) { x**2 }
Семантика лямбды и не-лямбды
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 = 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));
} Возвращает процедуру, являющуюся композицией этой процедуры и заданного 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() с заданными параметрами. Это синтаксический сахар для сокрытия “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() с заданными параметрами. Это синтаксический сахар для сокрытия “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() с заданными параметрами. Это синтаксический сахар для сокрытия “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 RBOOL(proc->is_lambda);
} Возвращает 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);
} Возвращает информацию об аргументах данного блока.
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;
} Помечает блок как проходящий ключевые аргументы через обычный разброс аргументов. Это следует вызывать только для блоков, которые принимают разброс аргументов (*args), но не явные ключевые аргументы или разброс ключевых аргументов. Это помечает блок так, что если блок вызывается с ключевыми аргументами, конечный хеш-аргумент помечается специальным флагом, таким образом, если он является последним элементом обычного разброса аргументов для другого вызова метода, и этот вызов метода не включает явные ключевые аргументы или разброс ключевых аргументов, конечный элемент интерпретируется как ключевые аргументы. Другими словами, ключевые аргументы будут переданы через блок другим методам.
Это следует использовать только для блоков, которые делегируют ключевые аргументы другому методу, и только для обратной совместимости с версиями Ruby до 2.7.
Этот метод, вероятно, будет удалён в какой-то момент, так как он существует только для обратной совместимости. Так как его нет в версиях Ruby до 2.7, убедитесь, что блок отвечает на этот метод перед его вызовом. Также имейте в виду, что если этот метод будет удалён, поведение блока изменится так, что он не будет передавать ключевые аргументы.
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-источнике, содержащие этот блок, или nil если этот блок не был определён в 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);
} Возвращает уникальный идентификатор для данного блока, а также указание на то, где он был определён.
Вызывает блок, устанавливая параметры блока на значения в параметрах, используя что-то близкое к семантике вызова метода. Возвращает значение последнего выражения, вычисленного в блоке.
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?.
Ruby Core © 1993–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.