1. ホーム
  2. スクリプト・コラム
  3. ルビートピックス

RubyのProcクラスとProc.newメソッドの使用法

2022-01-30 08:45:50

Procは、ブロックとそのコンテキスト(ローカル変数のスコープとスタックフレーム)をオブジェクト化して得られるプロシージャオブジェクトです。Proc は名前のない関数のように使用できますが、ローカル変数のスコープをインポートしません(動的ローカル変数を Proc のローカル変数として使用することは可能です)。

次の例では、Proc が常にローカル変数のスコープを維持しているため、まさに var 変数が呼び出されています。

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

def foo
 $foo.call
end

p foo # => 2



Proc を生成したメソッドから戻った後、Proc 内で戻りまたは再試行があると LocalJumpError 例外がスローされます。

def foo
 proc { return }
end

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

def foo
 proc { retry }
end

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



Proc の前に "&" をつけて、ブロックを持つメソッドに渡すと、ブロックの呼び出しと同じように動作します。しかし、厳密な意味では以下のような違いもあります。

# No problem.
(1..5).each { break }

# No problem in ruby 1.6.7, 1.8. In 1.6.8, an exception occurs
proc = Proc.new { break }
(1..5).each(&proc)

# LocalJumpError in ruby 1.6
# In ruby 1.8, run each again
proc = Proc.new { retry }
(1..5).each(&proc)
#=> retry from proc-closure (LocalJumpError)



これはまさにProcオブジェクトを呼び出しブロックとして使用した場合の制限です。

Proc.new
Proc.new { ... }


ブロックとそのコンテキストをオブジェクト化した結果を返します。

ブロックが与えられない場合、メソッドが呼び出されたブロックは Proc オブジェクトに変換されて返されます。

def foo
  pr = Proc.new
  pr.call(1,2,3)
end
foo {|args| p args }
# => [1, 2, 3]



Proc.newメソッドはより深く
Proc.new は、ブロックとそのコンテキストをオブジェクト化した後の結果を返します。

ブロックが与えられない場合、メソッドが呼び出されたブロックは Proc オブジェクトに変換されて返されます。

def foo
  pr = Proc.new
  pr.call(1,2,3)
end
foo {|args| p args }
# => [1, 2, 3]
This is the same as the following example
def foo
 yield(1,2,3)
end
foo {|args| p args }
# => [1, 2, 3]


主コールメソッドがブロックを運ばない場合、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


Proc.newを使用する場合、Proc#initializeメソッドが定義されていれば、オブジェクトの初期化時に呼び出されます。それ以外はproc.newと同じです。

ブロックを表すProcオブジェクトは、Proc.newメソッドを使用するか、procメソッドにブロックを指定して作成することができます。

ブロックはProc#callメソッドを呼び出すことで実行されます。Proc#callメソッドの呼び出しの引数はブロック変数として使用され、ブロック内の最後の式の値がProc#callの戻り値となります。proc#callはProc#[]という名前も持っています。

# Processing to determine if a year in the Western calendar is a leap year
leap = Proc.new do |year|
 year % 4 == 0 && year % 100 ! = 0 || year % 400 == 0
end
 
p leap.call(2000) #=> true
p leap[2013] #=> false
p leap[2016] #=> true


ブロック変数に|*配列|の形式を設定すると、メソッドの引数のように可変個数の引数を配列として受け取ることができます。

double = Proc.new do |*args|
 args.map{|i| i * 2 } # multiply all elements twice
end
 
p double.call(1, 2, 3) #=> [2, 3, 4]
p double[2, 3, 4] #=> [4, 6, 8]


このほか、デフォルトパラメータ、キーワードパラメータなど、共通メソッド定義時に使用できるパラメータ形式のほとんどすべてを、ブロック変数の定義で使用し、Proc#callメソッドに代入することができます。