title=手続きオブジェクトの挙動の詳細


 * [[ref:def]]
 * [[ref:should_use_next]]
 * [[ref:orphan]]
 * [[ref:block]]
 * [[ref:lambda_proc]]

===[a:def] 手続きオブジェクトとは

手続きオブジェクトとはブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)と
ともにオブジェクトしたものです。[[c:Proc]] クラスのインスタンスとして実現されています。

ブロック内では、新たなスコープが導入されるとともに、外側のローカル変数を参照できます。
Proc オブジェクトがローカル変数のスコープを保持していることは以下の例で変数 var を
参照できていることからわかります。

例:

 var = 1
 $foo = Proc.new { var }
 var = 2 
 
 def foo
   $foo.call
 end 

 p foo       # => 2

===[a:should_use_next] 手続きを中断して値を返す

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

例:

  def foo
    f = Proc.new{
      next 1
      2
    }
  end
  
 p foo().call       #=> 1

===[a:block] Proc オブジェクトをブロック付きメソッド呼び出しに使う

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

  # 問題なし
  (1..5).each { break }

  # LocalJumpError が発生します。
  pr = Proc.new { break }
  (1..5).each(&pr)         

  # each 再実行
  pr = Proc.new { retry }
  (1..5).each(&pr)

===[a:lambda_proc] lambda と proc と Proc.new とイテレータの違い

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

[[m:Kernel.#proc]] は lambda と同じになります。
引数に & を付けることで手続きオブジェクト化したブロックは、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 (2 for 3) (ArgumentError) 

[[ref:d:spec/call#block_arg]] も参照してください。

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

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     手続きオブジェクトを抜ける  手続きオブジェクトを抜ける   手続きオブジェクトを抜ける
  イテレータ メソッドを抜ける            手続きオブジェクトを抜ける   メソッドを抜ける



===[a:orphan] orphan な手続きオブジェクトの挙動

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

例:

  def foo
    Proc.new { return }
  end

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

  def foo
    Proc.new { retry }
  end

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

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

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