続・Ruby で labels できるか
先日、Ruby で labels に類似したパターンを書いたが、見た目も labels にできた。
まずは実例から。例によって階乗。
def fact(n) labels iter: -> m, acc { m==0 ? acc : iter(m-1, m*acc) } do iter(n, 1) end end fact(10) => 3628800
実装は次のようになる。
def labels(locals, &blk) namespace = Object.new namespace.instance_eval do locals.each_pair { |fn, body| define_singleton_method(fn, &body) } # Turn blk into a method so that the self in blk # points to namespace. define_singleton_method(:_main, &blk) _main end end
シンボルから手続きオブジェクトへのハッシュで局所関数を定義して、それらを新しい Object のメソッドとしてインストールしたあと、そのオブジェクトのコンテキストでブロックを実行する。
局所関数から親メソッドのインスタンス変数やメソッドに直接アクセスできないという問題はあるが、self がメソッドの名前空間とし働いている以上、ローカルなメソッド名との両立はできないだろう*1。
局所関数実行時の self を親メソッドと同じにすれば、局所関数の再帰呼び出しはできなくなるが、親の self のインスタンス変数などにアクセスできるようになる。ブロック引数は引き続き名前空間用のオブジェクトのコンテキストで実行される。
def flet(locals, &blk) _parent = self namespace = Object.new namespace.instance_eval do locals.each_pair { |fn, body| define_singleton_method(fn) do |*args| _parent.instance_eval { body.call(*args) } end } define_singleton_method(:_main, &blk) _main end end
def f @var = "hoge" flet g: -> { @var } do @var = "fuga" # g の中の @var には影響しない。 g end end f => "hoge"