class Proc

要約

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

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 は引数を多重代入に近い扱い方をします。

例:

b1 = Proc.new{|a,b,c|
  p a,b,c
}
b1.call(2, 4)
#=> 2
    4
    nil

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

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

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

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

例:

def foo
  f = Proc.new { return :foo }
  f.call
  return
end

def bar
  f = lambda { return :bar }
  f.call
  return
end

def h
  yield
end

def hoge
  h{ return :hoge }
  nil
end

p foo()  #=> :foo
p bar()  #=> nil
p hoge() #=> :hoge

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

               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]
new { ... } -> Proc

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

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

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[*arg] -> ()[permalink][rdoc]
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を生成したメソッドからリターンしてしまった場合に発生します。
arity -> Integer[permalink][rdoc]

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]

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

例:

def fred(param)
  proc {}
end

sample_proc = fred(99)
eval("param", sample_proc.binding)   # => 99
curry -> Proc[permalink][rdoc]
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.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
hash -> Integer[permalink][rdoc]

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

to_s -> String[permalink][rdoc]
inspect -> String

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

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

p Proc.new {
   true
}.to_s

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

手続きオブジェクトの引数の取扱が厳密であるならば 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]

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

source_location -> [String, Integer] | nil[permalink][rdoc]

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

その手続オブジェクトが 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]

self を返します。

例:

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