2012-04-07 9 views
13

mam ten kod:Zmień wiązanie Proc w Ruby

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

po prostu chcę, aby uzyskać dostęp a przez lambda i specjalny zakres która zdefiniowała a już gdzieś jak wewnątrz some_function w przykładzie, lub wkrótce później właśnie w takim samym zakresie jak:

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

Potem znalazłem podczas wywoływania l, wciąż jest za pomocą własnego wiązania, ale nie nowy, gdzie go nazywano.

A potem próbowałem go używać jako:

l.instance_eval do 
    a = 1 
    call 
end 

Ale to również nie jest to dziwne, że nie potrafi wyjaśnić dlaczego.

Wiem, że jednym z rozwiązań jest użycie eval, w którym mogłem specjalne wiązanie i wykonywanie niektórych kodu w tekście, ale naprawdę nie chcę używać jako tak.

I wiem, że jest w stanie użyć zmiennej globalnej lub zmiennej instancji. Jednak faktycznie mój kod znajduje się w głębszym środowisku osadzonym, więc nie chcę łamać ukończonych części, jeśli nie jest to konieczne.

W dokumentacji podałem nazwę klasy Proc i znalazłem nazwę funkcji binding, która odnosiła się do kontekstu Proc. Chociaż funkcja zapewniała tylko sposób uzyskania dostępu do wiązania, ale nie może go zmienić, z wyjątkiem korzystania z Binding#eval. Ocenia również tekst, który jest dokładnie tym, czego nie lubię robić.

Teraz pytanie brzmi, czy mam lepszy (lub bardziej elegancki) sposób realizacji tego? Lub używając eval jest już regularny regularny?

Edit aby odpowiedzieć @Andrew:
Ok, jest to problem, którego poznałem podczas Piszę leksykalny parser, w którym zdefiniowano tablicę ze stałej liczby elementów, nie w tym co najmniej Proc i wyrażenie regularne. Moim celem jest dopasowanie wyrażeń regularnych i wykonanie Proc do mojego specjalnego zakresu, w którym Proce będzie zawierał pewne zmienne lokalne, które powinny być zdefiniowane później. I wtedy spotkałem się z powyższym problemem.
Tak naprawdę to nie jest całkowicie takie same dla that question, jak moje jest przekazywanie w wiążące do Proc, a nie jak przekazać go się.

@Niklas: Dostałem odpowiedź, myślę, że właśnie tego chcę. Naprawdę rozwiązał mój problem.

+4

Być dobrym pytaniem jest dlaczego * * chcesz tego, czy raczej, co starasz się ostatecznie osiągnąć? –

+2

@Andrew: Nie wiem. Byłbym zainteresowany odpowiedzią na to dokładne pytanie, a nie na rozwiązanie podstawowego problemu :) –

+1

@NiklasB. Zgadzam się, ale znajomość celu końcowego może pomóc lepiej zrozumieć problem. I jestem pewien, że odpowiedziałem na podobne pytanie, ale próbuję je znaleźć, ale nie mogę ": ( –

Odpowiedz

19

Można wypróbować następujące Hack:

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

Aby być stosowany tak:

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 

Nie jest to bardzo ogólne rozwiązanie, choć.Byłoby lepiej, gdybyśmy mogli nadać mu Binding instancji zamiast Hash i wykonaj następujące czynności:

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

stosując następujące, bardziej skomplikowane hack, dokładne zachowanie to może być osiągnięte:

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 

Zasadniczo definiujemy sobie ręczny stos nazw i instance_exec nasz proces przeciwko niemu. To bardzo elastyczny mechanizm. To nie tylko umożliwia realizację call_with_binding, może być również wykorzystane do znacznie bardziej skomplikowane łańcuchy odnośników:

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) 

Drukuje 15, zgodnie z oczekiwaniami :)

UPDATE: Kod jest teraz również dostępny at Github. Używam tego również dla jednego z moich projektów.

+0

Niewielka nitpick: czy masz na myśli 'lambda {| a | local + func + some_method + a} '? Bardzo fajna odpowiedź! – Brandan

+0

@Brandan: Tak, zauważyłem to właśnie teraz Naprawiono. Dzięki! –

+0

@NiklasB. Hej przyjacielu, znalazłem lepsze i bardziej eleganckie rozwiązanie, zobacz mój kod poniżej! –

1
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 

a następnie użyć go jako:

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 
+0

O co ci chodzi? Mogą one być napisane jako 'instance_exec (& p)' lub 'Foo.new.instance_exec (& p)'. Myślę, że właśnie wymyśliłeś na nowo koło ... Moja odpowiedź miała być dużo bardziej ogólna, ale jeśli 'instance_exec' jest wystarczająca, to idź na to :) Metoda wykorzystująca metodę tymczasową nie jest szczególnie elegancka, ponieważ nie jest reentrant (jak w, nie może być używany w sposób zagnieżdżony, ani nie może być używany w wątkach). –

+2

Krótko mówiąc, nie uważam tego za "fajne", ale jeśli to działa dla ciebie, to fajnie :) –

+0

@NiklasB. Och, masz rację. Kiedyś zastanawiałem się, czy mogę się pozbyć oceny ciągów. Po przeczytaniu odpowiedzi poznałem instancję instance_exec, ale ... Naprawdę właśnie wymyślam nowe koło. –