Spec-Zone.ru › Ruby 4.0

класс Proc

Родительский класс:
Object

Объект 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 с семантикой lambda с помощью метода Kernel#lambda (объяснения о lambda приведены ниже):

    lambda1 = lambda {|x| x**2 }
    
  • Использовать синтаксис литерала lambda proc литерал lambda proc (он также создаёт proc с семантикой lambda):

    lambda2 = ->(x) { x**2 }
    

Семантика lambda и обычных proc

Proc бывают двух видов: lambda и обычные proc (не-lambda). Различия:

  • В lambda return и break означают выход из этой lambda;

  • В обычных proc return означает выход из охватывающего метода (и вызывает LocalJumpError, если вызван вне метода);

  • В обычных proc break означает выход из метода, которому был передан блок (и вызывает LocalJumpError, если вызван после возврата метода);

  • В lambda аргументы обрабатываются так же, как в методах: строго, с вызовом 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

Lambda полезны как самостоятельные функции, особенно в качестве аргументов функций высшего порядка, поскольку они ведут себя точно так же, как методы Ruby.

Proc полезны для реализации итераторов:

def test
  [[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
                            #  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
end

Внутри map блок кода рассматривается как обычный proc (не-lambda), а значит, внутренние массивы раскладываются на пары аргументов, а return завершает метод test. С более строгой lambda это было бы невозможно.

Отличить lambda от обычного proc можно с помощью метода экземпляра lambda?.

Семантика 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

Поскольку в lambda return и break завершают сам блок, lambda не могут осиротеть.

Анонимные параметры блока

Чтобы упростить запись коротких блоков, 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]]
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)

Использование неявных нумерованных параметров влияет на арность блока:

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 } }
#                    ^~

Общедоступные методы класса

new {|...| block } → a_proc Показать исходный код
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

Общедоступные методы экземпляра

prc << g → a_proc Показать исходный код
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#>>.

prc == other → true or 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.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;
}

Два 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
Также имеет псевдоним: eql?
===
Псевдоним для: call
prc >> g → a_proc Показать исходный код
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')

См. также Method#>> и Method#<<.

self[...] → obj

Вызывает блок, устанавливая его параметры в соответствии с аргументами, используя семантику, близкую к вызову метода. Возвращает значение последнего вычисленного выражения в блоке.

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».

Для 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?.

Псевдоним для: call
arity → integer Показать исходный код
static VALUE
proc_arity(VALUE self)
{
    int arity = rb_proc_arity(self);
    return INT2FIX(arity);
}

Возвращает количество обязательных аргументов. Если блок объявлен без аргументов, возвращает 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
binding → binding Показать исходный код
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
call(...) → obj Показать исходный код
0
static VALUE
proc_call(int argc, VALUE *argv, VALUE procval)
{
    /* removed */
}

Вызывает блок, устанавливая его параметры в соответствии с аргументами, используя семантику, близкую к вызову метода. Возвращает значение последнего вычисленного выражения в блоке.

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».

Для 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?.

Также имеет псевдонимы: [], ===, yield
curry → a_proc Показать исходный код
curry(arity) → a_proc
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 с переменным числом аргументов следует задавать необязательный аргумент 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
eql?(other) → true or false

Два 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
Псевдоним для: ==
hash → integer Показать исходный код
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.

inspect
Псевдоним для: to_s
lambda? → true or false Показать исходный код
VALUE
rb_proc_lambda_p(VALUE procval)
{
    rb_proc_t *proc;
    GetProcPtr(procval, proc);

    return RBOOL(proc->is_lambda);
}

Возвращает true, если объект Proc является lambda, и false, если это не-lambda.

Принадлежность к 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]

Объект 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? проверяет наличие этих особенностей. Если они не применяются, метод возвращает 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 определяет метод без таких особенностей.

parameters(lambda: nil) → array Показать исходный код
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, proc рассматривается как lambda при значении true и как не-lambda при значении false.

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]]
ruby2_keywords → proc Показать исходный код
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_post &&
                !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 post arguments 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 как передающий именованные аргументы через обычное раскрытие аргументов. Этот метод следует вызывать только для proc, принимающих раскрытие аргументов (*args), но не явные именованные аргументы и не их раскрытие. Метод помечает proc так, что при вызове с именованными аргументами последний аргумент-хеш получает специальный флаг. Если этот аргумент является последним элементом обычного раскрытия аргументов при вызове другого метода, а вызов не содержит явных именованных аргументов и их раскрытия, последний элемент интерпретируется как именованные аргументы. Иными словами, именованные аргументы будут передаваться через 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
source_location → [String, Integer, Integer, Integer, Integer] Показать исходный код
VALUE
rb_proc_location(VALUE self)
{
    return iseq_location(rb_proc_get_iseq(self, 0));
}

Возвращает место, где был определён Proc. Возвращённый Array содержит:

(1) the Ruby source filename
(2) the line number where the definition starts
(3) the column number where the definition starts
(4) the line number where the definition ends
(5) the column number where the definitions ends

Этот метод возвращает nil, если Proc был определён не в Ruby (то есть является встроенным).

to_proc → proc Показать исходный код
static VALUE
proc_to_proc(VALUE self)
{
    return self;
}

Часть протокола преобразования объектов в объекты Proc. Экземпляры класса Proc просто возвращают сами себя.

to_s → string Показать исходный код
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 и сведения о месте его определения.

Также имеет псевдоним: inspect
yield(...) → obj

Вызывает блок, устанавливая его параметры в соответствии с аргументами, используя семантику, близкую к вызову метода. Возвращает значение последнего вычисленного выражения в блоке.

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».

Для 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?.

Псевдоним для: call

Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API