2

次のコードがあります。

def call_block
  Proc.new.call
  my_local_proc = Proc.new { Proc.new.call }
  my_local_proc.call
end

call_block { p 'block' }

出力は次のとおりです。

block
block

Proc.new が call_block に渡したブロックをどのように見つけたのか、誰か説明してもらえますか? Proc.new は最も近いブロックを検索するだけで、完全に C++ で実装されていると思います。

もう 1 つ質問があります。Ruby だけでこのようなことが実現できるのでしょうか。つまり、ブロックが指定されていない場合、それを呼び出すメソッドに渡されたブロックを取得するようなメソッドを作成できますか。何かのようなもの:

def bar
  if not block_given?
    #use the block that has been given to the caller
  end
  # some code
end

def foo
  bar
end

foo { :block }
4

3 に答える 3

2

Proc.newメソッドのブロックがアタッチされているメソッド内でメソッドのブロックなしで呼び出された場合、メソッドのブロックを使用します。これは文書化された動作です。

YARV がどのようにそれを行うかを知るために、ソース コードを読んでみましょう。具体的には、proc_new関数:

block_pointer = rb_vm_control_frame_block_ptr(control_frame_pointer);

この行は、現在のコントロール フレームに関連付けられているブロックへのポインターを取得します。

これらの制御フレームは Ruby のスタックを実装していると思います。現在、Proc.newコントロール フレーム内にいるため、メソッドに指定されたブロックへのポインターを取得します。

if (block_pointer != NULL) {
    /* block found */
} else {
    /* block not found... */
}

ポインターが でない場合はNULLProc.new明示的にブロックが渡されました。しかし、ポインター NULLの場合はどうなるでしょうか。

/* block not found... */
control_frame_pointer = RUBY_VM_PREVIOUS_CONTROL_FRAME(control_frame_pointer);
block_pointer = rb_vm_control_frame_block_ptr(control_frame_pointer);

スタックを上に移動し、そのブロックを取得しようとします。つまり、呼び出し元のコントロール フレームに移動し、そのブロックを取得しようとします。

if (block_pointer != NULL) {
    if (is_lambda) {
        rb_warn("tried to create Proc object without a block");
    }
} else {
    rb_raise(rb_eArgError, "tried to create Proc object without a block");
}

そうでない場合はNULL、ほとんど成功しています。まだ の場合NULLは を作成できないため、 を発生ProcさせますArgumentError

アルゴリズムは次のようになります。

  1. Proc.newブロックが与えられた かどうかを確認する
    1. だったら使おう
    2. そうでない場合は、発信者がブロックされたかどうかを確認します
      1. だったら使おう
      2. そうでない場合は、エラーを発生させます

読みやすくするためにソース コードを変更しました。オリジナルについては、リンクされた GitHub のソース ファイルにアクセスしてください。

于 2012-12-02T18:18:23.030 に答える
1

ブロックなしで呼び出された場合Proc.new、メソッドに渡されたブロック (存在する場合) を受け取り、それを proc オブジェクトに変換します。

を呼び出すときにブロックを渡さないとfooArgumentError例外が発生しますが、これは十分合理的であり、block_given?false を返しProc.new、ブロックなしで使用しようとします。

&2 番目の質問の時点で、表記法を使用して多くのメソッドを介して proc を渡すことができます。

指定されたブロックを proc に変換し、さらに渡すことができます。

def bar &proc
  proc.call
end

def foo &proc
  bar &proc
end

p foo { :block }
# => :block

また、barメソッドは次のように書き換えることができます。

def bar
  yield if block_given?
end

yield指定されたブロックを実行するため、明示的に proc に変換したり、経由で実行したりする必要はありません。call

于 2012-12-02T18:18:56.837 に答える
0

説明書より。

現在のコンテキストにバインドされた新しい Proc オブジェクトを作成します。Proc::new は、ブロックが添付されたメソッド内でのみ、ブロックなしで呼び出すことができます。この場合、そのブロックは Proc オブジェクトに変換されます。

于 2012-12-02T18:17:58.740 に答える