2015-03-26 2 views
5

Refinements был экспериментальным дополнением к версии 2.0, а затем изменен и сделан постоянным в версии 2.1. Это дает возможность избежать «исправления обезьян», предоставляя «способ расширить класс локально».Использование уточнений иерархически

я попытался применить Refinements к this recent question которые я упростит таким образом:

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

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

Элемент по смещению i в a соответствует элементу по смещению i в b если:

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

и

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

Другими словами, соответствие строк не зависит от случая.

Проблема заключается в определении количества элементов a, соответствующих соответствующему элементу b. Мы видим, что ответ два, элементы в смещениях 1 и 2.

Один из способов сделать это обезьяны повязкой 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 

или вместо использования 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 

Однако, я хотел бы использовать Refinements так:

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

но, как вы видите, это дает неправильный ответ. Это потому, что я вызываю Array#==, и уточнение не применяется в пределах Array.

Я мог бы сделать это:

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 

, но это не то, что я хочу. Я хочу сделать что-то вроде этого:

module N 
    refine Array do 
    using M 
    end 
end 

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

но ясно это не работает.

Мой вопрос: есть ли способ уточнить String для использования в Array, а затем уточнить Array для использования в моем методе?

ответ

1

Вау, это было действительно интересно поиграть! Спасибо, что задали этот вопрос! Я нашел способ, который работает!

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 

Без переопределение == в Array, уточнение не будет применяться.Интересно, что это также не работает, если вы делаете это в двух отдельных модулях; это не работает, например:

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 

Я не достаточно хорошо знаком с деталями реализации refine быть абсолютно уверены, о том, почему происходит такое поведение. Я предполагаю, что внутренняя часть блока уточнения обрабатывается как входящая в другую область верхнего уровня, аналогично тому, как уточнения, определенные за пределами текущего файла, применяются только в том случае, если файл, в котором они определены, обрабатывается require в текущем файле , Это также объясняет, почему вложенные уточнения не работают; внутреннее убранство выходит из сферы действия в момент его выхода. Это также объяснить, почему обезьяна-латание Array как следует работе:

class Array 
    using M 

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

Это не пасть жертвой проблемы области видимости, что refine создает, таким образом, refine на String остается в области.

+0

Это замечательно! Одна деталь: вы можете рассмотреть возможность замены '! Self.zip (other) .map {| x, y | x == y} .include? false' с 'zip (other) .all? {| x, y | x == y} '. (Напомним, что 'self' является получателем по умолчанию.) –

+0

Ах, да, спасибо. Я приобрел плохую привычку использовать' self' везде, где он мог бы применяться. Это поможет мне вспомнить, имеет ли смысл использовать. Он выглядит намного лучше/читаем здесь без 'self' и используя' all? '. –

+0

Многие рубисты используют 'self', когда это не нужно, потому что они считают, что его упущение может ввести в заблуждение читателя. Я не в этом лагере, но я не могу сказать, что они неправы. –

Смежные вопросы