class Proc

[edit]

要約

ブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。

Proc は ローカル変数のスコープを導入しないことを除いて名前のない関数のように使えます。ダイナミックローカル変数は Proc ローカルの変数として使えます。

Proc がローカル変数のスコープを保持していることは以下の例で変数 var を参照できていることからわかります。



var = 1
$foo = Proc.new { var }
var = 2

def foo
  $foo.call
end

p foo       # => 2

手続きを中断して値を返す

手続きオブジェクトを中断して、呼出し元(呼び出しブロックでは yield、それ以外では Proc#call) へジャンプし値を返すには next を使います。break や return ではありません。



def foo
  f = Proc.new{
    next 1
    2              # この行に到達することはない
  }
end

p foo().call       #=> 1

Proc オブジェクトをブロック付きメソッド呼び出しに使う

ブロック付きメソッドに対して Proc オブジェクトを `&' を指定して渡すと呼び出しブロックのように動作します。しかし、厳密には以下の違いがあります。これらは、Proc オブジェクトが呼び出しブロックとして振舞う際の制限です。

問題なし

(1..5).each { break }
LocalJumpError が発生します。

pr = Proc.new { break }
(1..5).each(&pr)

lambda と proc と Proc.new とイテレータの違い

Kernel.#lambdaProc.new はどちらも Proc クラスのインスタンス(手続きオブジェクト)を生成しますが、生成された手続きオブジェクトはいくつかの場面で挙動が異なります。 lambda の生成する手続きオブジェクトのほうがよりメソッドに近い働きをするように設計されています。

Kernel.#proc は Proc.new と同じになります。引数に & を付けることで手続きオブジェクト化したブロックは、Proc.new で生成されたそれと同じように振る舞います。

引数の扱い

lambda のほうがより厳密です。引数の数が違っていると(メソッドのように)エラーになります。 Proc.new は引数を多重代入に近い扱い方をします。

Proc.new は引数の数が違っていてもエラーにならない

b = Proc.new{|a,b,c|
  p a,b,c
}
b.call(2, 4)
#=> 2
    4
    nil
lambda は引数の数が違うとエラーになる

b = lambda{|a,b,c|
  p a,b,c
}
b.call(2, 4)
# => wrong number of arguments (given 2, expected 3)

メソッド呼び出し(super・ブロック付き・yield)/ブロックパラメータの挙動 も参照してください。

ジャンプ構文の挙動の違い

return と break は、lambda と Proc.new では挙動が異なります。例えば return を行った場合、lambda では手続きオブジェクト自身を抜けますが、 Proc.new では手続きオブジェクトを囲むメソッドを抜けます。



def test_proc
  f = Proc.new { return :from_proc }
  f.call
  return :from_method
end

def test_lambda
  f = lambda { return :from_lambda }
  f.call
  return :from_method
end

def test_block
  tap { return :from_block }
  return :from_method
end

p test_proc()   #=> :from_proc
p test_lambda() #=> :from_method
p test_block()  #=> :from_block

以下の表は、手続きオブジェクトの実行を上の例と同じように、手続きオブジェクトが定義されたのと同じメソッド内で行った場合の結果です。

               return                          next                        break
Proc.new   メソッドを抜ける            手続きオブジェクトを抜ける   例外が発生する
proc       メソッドを抜ける            手続きオブジェクトを抜ける   例外が発生する
lambda     手続きオブジェクトを抜ける  手続きオブジェクトを抜ける   手続きオブジェクトを抜ける
イテレータ メソッドを抜ける            手続きオブジェクトを抜ける   手続きオブジェクトを抜ける

orphan な手続きオブジェクトの挙動

Proc を生成したメソッドから脱出した後、手続きオブジェクトからの return, break は例外 LocalJumpError を発生させます。ただし、上でも説明した通り lambda で生成した手続きオブジェクトはメソッドと同じように振る舞うことを意図されているため、例外 LocalJumpError は発生しません。



def foo
  Proc.new { return }
end

foo.call
# => in `call': return from proc-closure (LocalJumpError)

以下の表は、手続きオブジェクトの実行を上の例と同じように、手続きオブジェクトが定義されたメソッドを脱出してから行った場合の結果です。

               return                          next                        break
Proc.new   例外が発生する              手続きオブジェクトを抜ける   例外が発生する
proc       例外が発生する              手続きオブジェクトを抜ける   例外が発生する
lambda     手続きオブジェクトを抜ける  手続きオブジェクトを抜ける   手続きオブジェクトを抜ける

目次

特異メソッド
インスタンスメソッド

特異メソッド

new -> Proc[permalink][rdoc][edit]
new { ... } -> Proc

ブロックをコンテキストとともにオブジェクト化して返します。

ブロックを指定しない場合、Ruby 2.7 では $VERBOSE = true のときには警告メッセージ「warning: Capturing the given block using Proc.new is deprecated; use `&block` instead」が出力され、Ruby 3.0 では ArgumentError (tried to create Proc object without a block) が発生します。

ブロックを指定しなければ、このメソッドを呼び出したメソッドがブロックを伴うときに、それを Proc オブジェクトとして生成して返します。

ただし、ブロックを指定しない呼び出しは推奨されていません。呼び出し元のメソッドで指定されたブロックを得たい場合は明示的に & 引数でうけるべきです。

[EXCEPTION] ArgumentError:
スタック上にブロックがないのにブロックを省略した呼び出しを行ったときに発生します。


def foo
  pr = Proc.new
  pr.call(1)
end
foo {|arg| p arg }
# => 1

これは以下と同じです。



def foo
  yield(1)
end
foo {|arg| p arg }
# => 1

呼び出し元のメソッドがブロックを伴わなければ、例外 ArgumentError が発生します。



def foo
  Proc.new
end
foo
# => -:2:in `new': tried to create Proc object without a block (ArgumentError)
#        from -:2:in `foo'
#        from -:4:in `<main>'

Proc.new は、Proc#initialize が定義されていればオブジェクトの初期化のためにこれを呼び出します。このことを除けば、Kernel.#proc と同じです。

インスタンスメソッド

self << callable -> Proc[permalink][rdoc][edit]

self と引数を合成した Proc を返します。

戻り値の Proc は可変長の引数を受け取ります。戻り値の Proc を呼び出すと、まず受け取った引数を callable に渡して呼び出し、その戻り値を self に渡して呼び出した結果を返します。

Proc#>> とは呼び出しの順序が逆になります。

[PARAM] callable:
Proc、Method、もしくは任意の call メソッドを持ったオブジェクト。


f = proc { |x| x * x }
g = proc { |x| x + x }

# (3 + 3) * (3 + 3)
p (f << g).call(3) # => 36
call を定義したオブジェクトを渡す例

class WordScanner
  def self.call(str)
    str.scan(/\w+/)
  end
end

File.write('testfile', <<~TEXT)
  Hello, World!
  Hello, Ruby!
TEXT

pipeline = proc { |data| puts "word count: #{data.size}" } << WordScanner << File.method(:read)
pipeline.call('testfile') # => word count: 4

[SEE_ALSO] Method#<<, Method#>>

self[*arg] -> ()[permalink][rdoc][edit]
call(*arg) -> ()
self === *arg -> ()
yield(*arg) -> ()

手続きオブジェクトを実行してその結果を返します。

引数の渡され方はオブジェクトの生成方法によって異なります。詳しくは Proc#lambda? を参照してください。

「===」は when の所に手続きを渡せるようにするためのものです。



def sign(n)
  case n
  when lambda{|n| n > 0} then 1
  when lambda{|n| n < 0} then -1
  else 0
  end
end

p sign(-4) #=> -1
p sign(0)  #=> 0
p sign(7)  #=> 1

また、以下のような syntactic sugar もあります。



fib = lambda{|n|
  case n
  when 0 then 0
  when 1 then 1
  else
    fib.(n - 2) + fib.(n - 1)
  end
}
fib.(10) # => 55
[PARAM] arg:
手続きオブジェクトに与える引数を指定します。
[EXCEPTION] LocalJumpError:
Procを生成したメソッドからリターンしてしまった場合に発生します。
self >> callable -> Proc[permalink][rdoc][edit]

self と引数を合成した Proc を返します。

戻り値の Proc は可変長の引数を受け取ります。戻り値の Proc を呼び出すと、まず受け取った引数を self に渡して呼び出し、その戻り値を callable に渡して呼び出した結果を返します。

Proc#<< とは呼び出しの順序が逆になります。

[PARAM] callable:
Proc、Method、もしくは任意の call メソッドを持ったオブジェクト。


f = proc { |x| x * x }
g = proc { |x| x + x }

# (3 * 3) + (3 * 3)
p (f >> g).call(3) # => 18
call を定義したオブジェクトを渡す例

class WordScanner
  def self.call(str)
    str.scan(/\w+/)
  end
end

File.write('testfile', <<~TEXT)
  Hello, World!
  Hello, Ruby!
TEXT

pipeline = proc { |fname| File.read(fname) } >> WordScanner >> method(:p)
pipeline.call('testfile') # => ["Hello", "World", "Hello", "Ruby"]

[SEE_ALSO] Method#<<, Method#>>

arity -> Integer[permalink][rdoc][edit]

Proc オブジェクトが受け付ける引数の数を返します。

ただし、可変長引数を受け付ける場合、負の整数

-(必要とされる引数の数 + 1)

を返します。



lambda{           }.arity   # => 0
lambda{||         }.arity   # =>  0
lambda{|x|        }.arity   # =>  1
lambda{|*x|       }.arity   # => -1
lambda{|x, y|     }.arity   # =>  2
lambda{|x, *y|    }.arity   # => -2
lambda{|(x, y)|   }.arity   # =>  1
lambda{|(x, y), z|}.arity   # =>  2
binding -> Binding[permalink][rdoc][edit]

Proc オブジェクトが保持するコンテキストを Binding オブジェクトで返します。



def fred(param)
  proc {}
end

sample_proc = fred(99)
eval("param", sample_proc.binding)   # => 99
curry -> Proc[permalink][rdoc][edit]
curry(arity) -> Proc

Procをカリー化します

カリー化したProcはいくつかの引数をとります。十分な数の引数が与えられると、元のProcに引数を渡して実行し、結果を返します。引数の個数が足りないときは、部分適用したカリー化Procを返します。

[PARAM] arity:
引数の個数を指定します
[RETURN]
カリー化した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.sum }
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.sum }
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
hash -> Integer[permalink][rdoc][edit]

self のハッシュ値を返します。

Proc オブジェクトの引数の情報を返します。

Proc オブジェクトが引数を取らなければ空の配列を返します。引数を取る場合は、配列の配列を返し、各配列の要素は引数の種類に対応した以下のような Symbol と、引数名を表す Symbol の 2 要素です。


prc = proc{|x, y=42, *other|}
p prc.parameters  # => [[:opt, :x], [:opt, :y], [:rest, :other]]
prc = lambda{|x, y=42, *other|}
p prc.parameters  # => [[:req, :x], [:opt, :y], [:rest, :other]]
prc = proc{|x, y=42, *other|}
p prc.parameters(lambda: true)  # => [[:req, :x], [:opt, :y], [:rest, :other]]
prc = lambda{|x, y=42, *other|}
p prc.parameters(lambda: false) # => [[:opt, :x], [:opt, :y], [:rest, :other]]
to_s -> String[permalink][rdoc][edit]
inspect -> String

self の文字列表現を返します。

可能なら self を生成したソースファイル名、行番号を含みます。



p Proc.new {
   true
}.to_s

# => "#<Proc:0x0x401a880c@-:3>"
lambda? -> bool[permalink][rdoc][edit]

手続きオブジェクトの引数の取扱が厳密であるならば true を返します。

引数の取扱の厳密さの意味は以下の例を参考にしてください。



# lambda で生成した Proc オブジェクトでは true 
lambda{}.lambda? # => true
# proc で生成した Proc オブジェクトでは false
proc{}.lambda?   # => false
# Proc.new で生成した Proc オブジェクトでは false
Proc.new{}.lambda? # => false

# 以下、lambda?が偽である場合
# 余分な引数を無視する
proc{|a,b| [a,b]}.call(1,2,3) # => [1,2]
# 足りない引数には nil が渡される
proc{|a,b| [a,b]}.call(1) # => [1, nil]
# 配列1つだと展開される
proc{|a,b| [a,b]}.call([1,2]) # => [1,2]
# lambdaの場合これらはすべて ArgumentError となる
 
# &が付いた仮引数で生成される Proc は lambda? が偽となる
def n(&b) b.lambda? end
n {} # => false

# &が付いた実引数によるものは、lambda?が元の 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

n(&lambda {})                #=> true
n(&proc {})                  #=> false
n(&Proc.new {})              #=> false

# Method#to_proc によるものは lambda?が真となる
def m() end
method(:m).to_proc.lambda? #=> true

# Module#define_method は特別扱いで、
# これで定義されたメソッドの引数は常に厳密に取り扱われる
class C
  define_method(:d) {}
end
C.new.d(1,2)       #=> ArgumentError
C.new.method(:d).to_proc.lambda?   #=> true

class C
  define_method(:e, &proc {})
end
C.new.e(1,2)       #=> ArgumentError
C.new.method(:e).to_proc.lambda?   #=> true
parameters -> [object][permalink][rdoc][edit]

Proc オブジェクトの引数の情報を返します。

Proc オブジェクトが引数を取らなければ空の配列を返します。引数を取る場合は、配列の配列を返し、各配列の要素は引数の種類に対応した以下のような Symbol と、引数名を表す Symbol の 2 要素です。

:req

必須の引数

:opt

デフォルト値が指定されたオプショナルな引数

:rest

* で指定された残りすべての引数

:keyreq

必須のキーワード引数

:key

デフォルト値が指定されたオプショナルなキーワード引数

:keyrest

** で指定された残りのキーワード引数

:block

& で指定されたブロック引数



prc = lambda{|x, y=42, *other, k_x:, k_y: 42, **k_other, &b|}
prc.parameters #=> [[:req, :x], [:opt, :y], [:rest, :other], [:keyreq, :k_x], [:key, :k_y], [:keyrest, :k_other], [:block, :b]]

[SEE_ALSO] Method#parameters, UnboundMethod#parameters

ruby2_keywords -> proc[permalink][rdoc][edit]

Marks the proc as passing keywords through a normal argument splat. This should only be called on procs that accept an argument splat (`*args`) but not explicit keywords or a keyword splat. It marks the proc such that if the proc is called with keyword arguments, the final hash argument is marked with a special flag such that if it is the final element of a normal argument splat to another method call, and that method call does not include explicit keywords or a keyword splat, the final element is interpreted as keywords. In other words, keywords will be passed through the proc to other methods.

This should only be used for procs that delegate keywords to another method, and only for backwards compatibility with Ruby versions before 2.7.

This method will probably be removed at some point, as it exists only for backwards compatibility. As it does not exist in Ruby versions before 2.7, check that the proc responds to this method before calling it. Also, be aware that if this method is removed, the behavior of the proc will change so that it does not pass through keywords.


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] | nil[permalink][rdoc][edit]

ソースコードのファイル名と行番号を配列で返します。

その手続オブジェクトが ruby で定義されていない(つまりネイティブである)場合は nil を返します。



# /path/to/target.rb を実行
proc {}.source_location            # => ["/path/to/target.rb", 1]
proc {}.source_location            # => ["/path/to/target.rb", 2]
(eval "proc {}").source_location   # => ["(eval)", 1]
method(:p).to_proc.source_location # => nil

[SEE_ALSO] Method#source_location

to_proc -> self[permalink][rdoc][edit]

self を返します。



pr = proc {}
p pr == pr.to_proc # => true