2015-03-26 10 views
5

Refinements było eksperymentalnym dodatkiem do wersji 2.0, a następnie zmodyfikowane i wprowadzone na stałe w wersji 2.1. Zapewnia to sposób na uniknięcie "łatania małp" poprzez zapewnienie "sposobu na rozszerzenie klasy lokalnie".Użycie opcji Zawężenie hierarchicznie

próbowałem zastosować Refinements do this recent question które będę uprościć sposób:

a = [[1, "a"], 
    [2, "b"], 
    [3, "c"], 
    [4, "d"]] 

b = [[1, "AA"], 
    [2, "B"], 
    [3, "C"], 
    [5, "D"]] 

elementu o przesunięcie i w a pasuje do elementu w offsecie i w b jeżeli:

a[i].first == b[i].first 

i

a[i].last.downcase == b[i].last.downcase 

Innymi słowy, dopasowanie łańcuchów jest niezależne od wielkości liter.

Problem polega na określeniu liczby elementów a pasujących do odpowiedniego elementu z b. Widzimy, że odpowiedź jest dwa, elementy na offsetach 1 i 2.

Jednym ze sposobów, aby to zrobić jest małpa plastra String#==:

class String 
    alias :dbl_eql :== 
    def ==(other) 
    downcase.dbl_eql(other.downcase) 
    end 
end 

a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } } 
    #=> 2 

lub zamiast wykorzystać Refinements:

module M 
    refine String do 
    alias :dbl_eql :== 
    def ==(other) 
     downcase.dbl_eql(other.downcase) 
    end 
    end 
end 

'a' == 'A' 
    #=> false (as expected) 
a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } } 
    #=> 0 (as expected) 

using M 
'a' == 'A' 
    #=> true 
a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } } 
    #=> 2 

Jednak chciałbym skorzystać Refinements tak:

using M 
a.zip(b).count { |ae,be| ae == be } 
    #=> 0 

ale, jak widzisz, daje to złą odpowiedź. To dlatego, że wywołuję Array#==, a udoskonalenie nie ma zastosowania w obrębie Array.

mogę to zrobić:

module N 
    refine Array do 
    def ==(other) 
     zip(other).all? do |ae,be| 
     case ae 
     when String 
      ae.downcase==be.downcase 
     else 
      ae==be 
     end 
     end 
    end 
    end 
end 

using N 
a.zip(b).count { |ae,be| ae == be } 
    #=> 2 

ale to nie to, co chcę. Chcę zrobić coś takiego:

module N 
    refine Array do 
    using M 
    end 
end 

using N 
a.zip(b).count { |ae,be| ae == be } 
    #=> 0 

ale oczywiście to nie działa.

Moje pytanie: czy istnieje sposób na udoskonalenie String do użytku w Array, a następnie doprecyzowanie Array do wykorzystania w mojej metodzie?

Odpowiedz

1

Wow, to było naprawdę interesujące do zabawy! Dziękuję za pytanie! Znalazłem sposób, który działa!

module M 
    refine String do 
    alias :dbl_eql :== 
     def ==(other) 
     downcase.dbl_eql(other.downcase) 
     end 
    end 

    refine Array do 
    def ==(other) 
     zip(other).all? {|x, y| x == y} 
    end 
    end 
end 

a = [[1, "a"], 
    [2, "b"], 
    [3, "c"], 
    [4, "d"]] 

b = [[1, "AA"], 
    [2, "B"], 
    [3, "C"], 
    [5, "D"]] 

using M 

a.zip(b).count { |ae,be| ae == be } # 2 

Bez redefinicji == w Array, wyrafinowanie nie będą miały zastosowania.Co ciekawe, to również nie działa, jeśli robisz to w dwóch oddzielnych modułach; to nie działa, na przykład:

module M 
    refine String do 
    alias :dbl_eql :== 
     def ==(other) 
     downcase.dbl_eql(other.downcase) 
     end 
    end 
end 

using M 

module N 
    refine Array do 
    def ==(other) 
     zip(other).all? {|x, y| x == y} 
    end 
    end 
end 

a = [[1, "a"], 
    [2, "b"], 
    [3, "c"], 
    [4, "d"]] 

b = [[1, "AA"], 
    [2, "B"], 
    [3, "C"], 
    [5, "D"]] 

using N 

a.zip(b).count { |ae,be| ae == be } # 0 

nie jestem wystarczająco zaznajomiony ze szczegółami implementacji refine być całkowicie przekonani o tym, dlaczego to zachowanie występuje. Domyślam się, że wnętrze bloku rafinacji jest traktowane jako wprowadzanie innego zakresu najwyższego poziomu, podobnie do tego, w jaki sposób definicje zdefiniowane poza bieżącym plikiem mają zastosowanie tylko wtedy, gdy plik, w którym są zdefiniowane, jest analizowany w bieżącym pliku za pomocą require . To również wyjaśnia, dlaczego zagnieżdżone rafinacje nie działają; dopracowanie wnętrza wykracza poza zakres w momencie jego wyjścia. To również wyjaśnić, dlaczego małpa-łatanie Array jako następujące prace:

class Array 
    using M 

    def ==(other) 
    zip(other).all? {|x, y| x == y} 
    end 
end 

nie paść ofiarą kwestii zakresu ich że refine tworzy, więc refine na String pozostaje w zakresie.

+0

To świetnie! Jeden szczegół: możesz rozważyć zastąpienie '! Self.zip (other) .map {| x, y | x == y} .include? false' with 'zip (other) .all? {| x, y | x == y} '. (Przypomnijmy, że 'self' jest domyślnym odbiornikiem.) –

+0

Ach, tak, dzięki - nabrałem złego nawyku używania" ja "wszędzie, gdzie mogłoby to mieć zastosowanie. To pomoże mi zapamiętać, czy warto korzystać z niego, czy nie. Wygląda o wiele ładniej/bardziej czytelnie, bez 'self' i używania' all? '. –

+0

Wielu Rubyists używają "self", gdy nie jest potrzebne, ponieważ wierzą, że jego pominięcie może być mylące dla czytelnika. Nie jestem w tym obozie, ale nie mogę powiedzieć, że są w błędzie. –

Powiązane problemy