Ruby Ampersand Method
Is there a way to override the &
method on an object so it returns an array when called li开发者_开发技巧ke so:
class MyObject
end
o1, o2, o3 = MyObject.new, MyObject.new, MyObject.new
o1 # => #<MyObject0x01>
o1 & o2 # => [#<MyObject0x01>, #<MyObject0x02>]
o1 & o2 & o3 # => [#<MyObject0x01>, #<MyObject0x02>, #<MyObject0x03>]
Trying to make your own syntax seems silly. Why wouldn't you just use Ruby's array notation?
[o1, o2, o3] # => [#<MyObject0x01>, #<MyObject0x02>, #<MyObject0x03>]
Am I overlooking something obvious here?
The simple answer is no. The lengthy answer is if you return an Array you will break the reference chain of MyObject, which then starts calling methods on Array. The best solutions in order of desirability based on The Principal of least surprise
Use
[] << o1 << o2 << o3
or[o1,o2,o3]
define
<<
to behave like an ArrayCreate a class such as
class MyObjectCollection < Array def & # do what I mean end end
Monky patch Array
Explanation of reference chaining
class MyObject
end
o1, o2, o3 = MyObject.new, MyObject.new, MyObject.new
o1 # => #<MyObject0x01> # <-- works
o1 & o2 # => [#<MyObject0x01>, #<MyObject0x02>] # <-- could work with def & because the LHS is a MyObject
o1 & o2 & o3 # => [#<MyObject0x01>, #<MyObject0x02>, #<MyObject0x03>] # <--- CANNOT work because o1 & o2 return an object of type Array and not of type MyObject
Read all the other answers for important details. But here is a solution:
class MyObject
def initialize(x)
@x = x
end
def &(arg)
return [self, arg]
end
def to_s
@x
end
end
class Array
def &(arg)
if arg.is_a? MyObject
return self << arg
else
# do what Array.& would normally do
end
end
end
a = MyObject.new('a')
b = MyObject.new('b')
c = MyObject.new('c')
x = a & b & c
puts x.class
puts "[#{x.join(', ')}]"
Here is another solution that is safer (i.e. no monkeypatching):
class MyObject
def initialize(x)
@x = x
end
def &(arg)
a = MyObjectArray.new
a << self << arg
end
def to_s
@x
end
end
class MyObjectArray < Array
def &(arg)
return self << arg
end
end
a = MyObject.new('a')
b = MyObject.new('b')
c = MyObject.new('c')
x = a & b & c
puts x.class
puts "[#{x.join(', ')}]"
I suppose, but then you would also have to monkeypatch Array
, and then you would have a clash because Array
already defines &
.
Why not define <<
instead? Then after the first operator returns [o1, o2]
, the <<
that Array
already has will DTRT for you.
>> class MyObject
>> def << x
>> raise ArgumentError unless x.is_a? self.class or is_a? x.class
>> [self, x]
>> end
>> end
精彩评论