Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby: proc(lamda)を定義時ではなく呼び出したインスタンスのコンテキストで実行する方法MEMO

Proc(lamda)を定義時ではなく実行時のコンテキスで実行する際にちょっとハマったのでメモ📝

結論: instance_exec(&proc)で実行すれば良かった。

以下のような外部で定義したprocを受け取って実行した際にhello! by Caller.と出ることを期待したのですが実行するとNameErrorが発生します。 下記の通りProcは定義時のコンテキストで作成されますが、

class Caller
  def initialize(msg)
    @msg = msg
  end

  attr_reader :msg

  def callProc(targetProc)
    puts targetProc.call
  end
end

sampleProc = -> { "hello! by #{msg}." }
puts Caller.new('Caller').callProc(sampleProc)
# => undefined local variable or method `msg' for main:Object (NameError)

これは以下の通りProcが定義時のコンテキストを保持しており、そのコンテキストで実行されるためmainのmsgを探索してしまい変数が見つからずエラーになってしまいます。

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

そのためinstance_execで呼び出したインスタンスのコンテキストで実行すれば変数が見つかり期待通りの出力を得ることが出来ました!

class Caller
  def initialize(msg)
    @msg = msg
  end

  attr_reader :msg

  def callProc(targetProc)
    instance_exec(&targetProc)
  end
end

sampleProc = -> { "hello! by #{msg}." }
puts Caller.new('Caller').callProc(sampleProc)
#=> hello! by Caller.

docs.ruby-lang.org

ちなみにinstance_eval(&targetProc)の場合には、instance_evalがデフォルトでobj(実行時のself)をblock引数として渡してしまうので、lamdaだと引数が合わずにArgumentErrorが発生します。

docs.ruby-lang.org

参考

secret-garden.hatenablog.com

qiita.com