Refinements was an experimental addition to v2.0, then modified and made permanent in v2.1. It provides a way to avoid "monkey-patching" by providing "a way to extend a class locally".
I attempted to apply Refinements
to this recent question which I will simplify thus:
a = [[1, "a"],
[2, "b"],
[3, "c"],
[4, "d"]]
b = [[1, "AA"],
[2, "B"],
[3, "C"],
[5, "D"]]
The element at offset i
in a
matches the element at offset i
in b
if:
a[i].first == b[i].first
and
a[i].last.downcase == b[i].last.downcase
In other words, the matching of the strings is independent of case.
The problem is to determine the number of elements of a
that match the corresponding element of b
. We see that the answer is two, the elements at offsets 1
and 2
.
One way to do this is to monkey-patch 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
or instead use 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
However, I would like to use Refinements
like this:
using M
a.zip(b).count { |ae,be| ae == be }
#=> 0
but, as you see, that gives the wrong answer. That's because I'm invoking Array#== and the refinement does not apply within Array
.
I could do this:
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
but that's not what I want. I want to do something like this:
module N
refine Array do
using M
end
end
using N
a.zip(b).count { |ae,be| ae == be }
#=> 0
but clearly that does not work.
My question: is there a way to refine String
for use in Array
, then refine Array
for use in my method?