21

私はこのコードを持っています:

 l = lambda { a }
 def some_function
     a = 1
 end

aラムダと、例のa内部のようにすでに定義されている特別なスコープsome_function、またはすぐ後に同じスコープでアクセスしたいだけです。

 l = lambda { a }
 a = 1
 l.call

次に、を呼び出したときにl、それはまだ独自のバインディングを使用していますが、呼び出された新しいバインディングは使用していないことがわかりました。

そして、私はそれを次のように使用しようとしました:

 l.instance_eval do
     a = 1
     call
 end

しかし、これも失敗しました。理由を説明できないのは不思議です。

解決策の1つがを使用していることは知っています。この場合eval、バインディングを特殊化してテキストでコードを実行できますが、実際にはそのように使用したくありません。

そして、私はそれがグローバル変数またはインスタンス変数を使用できることを知っています。ただし、実際には私のコードはより深い組み込み環境にあるので、必要がなければ完成したパーツを壊したくありません。

Procドキュメントでクラスを参照しましたが、のコンテキストを参照する関数名を見つけましたbindingProc関数はそのバインディングにアクセスする方法を提供するだけでしたが、を使用する場合を除いて、それを変更することはできませんBinding#eval。テキストも評価しますが、これは私がやりたくないことです。

ここで問題は、これを実装するためのより良い(またはよりエレガントな)方法がありますか?または、使用evalはすでに通常の方法ですか?

@Andrewに返信するために編集してください:
さて、これは字句パーサーを書いているときに遭遇した問題です。そこでは、少なくともaProcと正規表現を含む固定数の項目で配列を定義しました。私の目的は、正規表現を照合し、特別なスコープでProcsを実行することです。ここで、Proceには、後で定義する必要のあるいくつかのローカル変数が含まれます。そして、私は上記の問題に遭遇しました。実際、私はその質問
と完全に同じではないと思います。私のものは、Procを渡す方法ではなく、Procバインドする方法を渡す方法だからです。

@Niklas:あなたの答えを得ました、私はそれがまさに私が欲しいものだと思います。それは私の問題を完全に解決しました。

4

4 に答える 4

26

次のハックを試すことができます。

class Proc
  def call_with_vars(vars, *args)
    Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
  end
end

このように使用するには:

irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3)
=> 3
irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1)
=> 4

ただし、これはあまり一般的な解決策ではありません。Bindingハッシュの代わりにインスタンスを与えて、次のようにするとよいでしょう。

l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1)  # => 4

次のより複雑なハックを使用すると、この正確な動作を実現できます。

class LookupStack
  def initialize(bindings = [])
    @bindings = bindings
  end

  def method_missing(m, *args)
    @bindings.reverse_each do |bind|
      begin
        method = eval("method(%s)" % m.inspect, bind)
      rescue NameError
      else
        return method.call(*args)
      end
      begin
        value = eval(m.to_s, bind)
        return value
      rescue NameError
      end
    end
    raise NoMethodError
  end

  def push_binding(bind)
    @bindings.push bind
  end

  def push_instance(obj)
    @bindings.push obj.instance_eval { binding }
  end

  def push_hash(vars)
    push_instance Struct.new(*vars.keys).new(*vars.values)
  end

  def run_proc(p, *args)
    instance_exec(*args, &p)
  end
end

class Proc
  def call_with_binding(bind, *args)
    LookupStack.new([bind]).run_proc(self, *args)
  end
end

基本的に、手動の名前ルックアップスタックとinstance_execそれに対するprocを定義します。これは非常に柔軟なメカニズムです。の実装を可能にするだけでなく、call_with_bindingはるかに複雑なルックアップチェーンを構築するためにも使用できます。

l = lambda { |a| local + func(2) + some_method(1) + var + a }

local = 1
def func(x) x end

class Foo < Struct.new(:add)
  def some_method(x) x + add end
end

stack = LookupStack.new
stack.push_binding(binding)
stack.push_instance(Foo.new(2))
stack.push_hash(:var => 4)

p stack.run_proc(l, 5)

これは予想通り15を出力します:)

更新:コードはGithubでも利用できるようになりました。私はこれを私のプロジェクトの1つにも使用しています。

于 2012-04-07T23:29:24.673 に答える
2
class Proc
    def call_with_obj(obj, *args)
        m = nil
        p = self
        Object.class_eval do
            define_method :a_temp_method_name, &p
            m = instance_method :a_temp_method_name; remove_method :a_temp_method_name
        end
        m.bind(obj).call(*args)
    end
end

そしてそれを次のように使用します:

class Foo
    def bar
        "bar"
    end
end

p = Proc.new { bar }

bar = "baz"

p.call_with_obj(self) # => baz
p.call_with_obj(Foo.new) # => bar
于 2012-05-19T23:42:28.143 に答える
1

おそらく、実際には後で定義する必要はありませんが、代わりに後で設定aするだけで済みます。

または(以下のように)、おそらく実際にaはローカル変数(それ自体が配列を参照する)である必要はありません。代わりに、おそらく、などのクラス変数を使用すると便利です@@a。これは、「1」を出力することで機能します。

class SomeClass
  def l
    @l ||= lambda { puts @@a }
  end

  def some_function
    @@a = 1
    l.call
  end
end
SomeClass.new.some_function
于 2019-10-25T16:35:37.990 に答える
0

同様の方法:

class Context
  attr_reader :_previous, :_arguments

  def initialize(_previous, _arguments)
    @_previous = _previous
    @_arguments = _arguments
  end
end

def _code_def(_previous, _arguments = [], &_block)
  define_method("_code_#{_previous}") do |_method_previous, _method_arguments = []|
    Context.new(_method_previous, _method_arguments).instance_eval(&_block)
  end
end

_code_def('something') do
  puts _previous
  puts _arguments
end
于 2019-05-16T12:44:16.363 に答える