2015-02-21 18 views
7

Istnieje całkiem dobra dokumentacja aktualnej implementacji udoskonaleń w ruby ​​tutaj: http://ruby-doc.org//core-2.2.0/doc/syntax/refinements_rdoc.html, , ale są pewne dziwne przypadki narożne.Subtelności subtelności rubinu

Po pierwsze, include module jest ortogonalna do using module (jedna zawiera metodę instancji modułu, a druga aktywuje udoskonalenie). Ale jest też pewna sztuczka, aby zawrzeć sam moduł udoskonalania, zobacz Better way to turn a ruby class into a module than using refinements?.

def to_module(klass) 
    Module.new do 
    #note that we return the refinement module itself here 
    return refine(klass) { 
     yield if block_given? 
    } 
    end 
end 

class Base 
    def foo 
    "foo" 
    end 
end 
class Receiver 
    include to_module(Base) { 
    def foo 
     "refined " + super 
    end 
    } 
end 
Receiver.new.foo #=> "refined foo" 

dziwo ten moduł dystynkcja nie może być używany z using!

m=to_module(Base) {} 
m.class #=> Module 
using m  
#=>TypeError: wrong argument type Class (expected Module) 

Wykorzystanie tylko pracy w module otaczającym modułów udoskonalania. Po drugie, chciałem wykorzystać powyższy trik wydajności, aby móc przekazać Proc do udoskonalenia (nawet poprzez to, że akceptuje tylko blok), bez uciekania się do konwersji Proc z powrotem do źródła, jak w https://www.new-bamboo.co.uk/blog/2014/02/05/refinements-under-the-knife/. Ale używając yield jak na przykład obejmować nie działa:

def ref_module1(klass) 
    Module.new do 
    refine(klass) { 
     yield 
    } 
    end 
end 

class Receiver1 
    using ref_module1(Base) { 
    def foo 
     "refined " + super 
    end 
    } 
    def bar 
    Base.new.foo 
    end 
end 
Receiver1.new.bar #=> NoMethodError: super: no superclass method `foo' 

Widzimy, że Receiver1 nadal korzystać Bar foo, a nie sposób wyrafinowany. howewer możemy użyć module_eval zamiast:

def ref_module2(klass,&b) 
    Module.new do 
    refine(klass) { 
     module_eval(&b) 
    } 
    end 
end 

class Receiver2 
    using ref_module2(Base) { 
    def foo 
     "refined " + super 
    end 
    } 
    def bar 
    Base.new.foo 
    end 
end 
Receiver2.new.bar #=> "refined foo" 

ja nie bardzo rozumiem dlaczego module_eval pracuje tutaj, a nie metodę yield. Wewnątrz bloku udoskonalania, "default_definee" jest modułem udokładniania, więc module_eval, który umieszcza "default_definee" na self = "moduł udokładniania", nie powinien mieć na nie wpływu. I rzeczywiście w przykładzie "uwzględnij" na samym początku otrzymuję taki sam wynik, gdy używam module_eval lub bezpośrednio yield.

Czy ktoś może wyjaśnić to zachowanie?

+0

Świetne pytanie. Mam nadzieję, że otrzymasz równie dobre odpowiedzi. –

Odpowiedz

3

Kontekst (lub powiązanie) jest powodem działania modułu module, a wydajność nie występuje w ostatnim zestawie przykładów. Nie ma to nic wspólnego z udoskonaleniami, co pokazano poniżej.

Począwszy module_eval:

class Foo 
    def run(&block) 
    self.class.module_eval(&block) 
    end 
end 

foo = Foo.new 
foo.run { 
    def hello 
    "hello" 
    end 
} 

puts foo.hello # => "hello" 
puts hello => # '<main>': undefined method 'hello' for main:Object (NameError) 

W Foo#run nazywamy module_eval na Foo. Spowoduje to zmianę kontekstu (self) na Foo. Rezultat jest podobny do tego, jak pierwotnie zdefiniowaliśmy proste ustawienie hello wewnątrz.

Teraz rzućmy okiem na yield:

class Foo 
    def run 
    yield 
    end 
end 

foo = Foo.new 
foo.run { 
    def hello 
    "hello" 
    end 
} 

puts hello # => "hello" 
puts foo.hello # => '<main>': private method 'hello' called for ... 

yield prostu wywołuje blok w oryginalnym kontekście, który w tym przykładzie byłby <main>.Gdy blok jest wywoływany, końcowy wynik jest dokładnie taka sama jak metoda zostały określone na najwyższym poziomie normalnie:

class Foo 
    def run 
    yield 
    end 
end 

foo = Foo.new 

def hello 
    "hello" 
end 

puts hello # => "hello" 
puts foo.hello # => '<main>': private method 'hello' called for ... 

Można zauważyć, że foo wydaje się mieć metodę w przykładach yieldhello. Jest to efekt uboczny definiowania hello jako metody na najwyższym poziomie. Okazuje się, że <main> jest tylko instancją Object, a definiowanie metod najwyższego poziomu jest po prostu definiowaniem prywatnych metod na Object, które prawie wszystko inne kończy się dziedziczeniem. Możesz to zobaczyć otwierając irb i uruchamiając następujące:

self  # => main 
self.class # => Object 

def some_method 
end 

"string".method(:some_method) # => #<Method: String(Object)#some_method> 

Teraz wróć do swoich przykładów.

Oto, co się dzieje na przykład yield:

def ref_module1(klass) 
    Module.new do 
    refine(klass) { 
     yield 
    } 
    end 
end 

class Receiver1 
    # like my yield example, this block is going to 
    # end up being invoked in its original context 
    using ref_module1(Base) { 
    def foo 
     "I'm defined on Receiver1" 
    end 
    } 

    def bar 
    # calling foo here will simply call the original 
    # Base#foo method 
    Base.new.foo 
    end 
end 

# as expected, if we call Receiver1#bar 
# we get the original Base#foo method 
Receiver1.new.bar # => "foo" 

# since the block is executed in its original context 
# the method gets defined in Receiver1 -- its original context 
Receiver1.new.foo # => "I'm defined on Receiver1" 

chodzi o module_eval, działa w swoich przykładach, ponieważ powoduje blok być uruchamiany w kontekście nowego modułu, a nie od klasy Receiver1 .

+0

Masz rację, ponieważ blok jest zamknięciem, będzie zawierał bieżące wartości 'self' i 'klass', więc musimy użyć module_eval, aby ustawić 'klass' na anonimowy moduł. Byłem zdezorientowany faktem, że pierwszy przykład działał, ale faktycznie "foo" jest zdefiniowany na odbiorniku, a nie na anonimowym module, jak myślałem. Dzięki! –