开发者

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

  1. Use [] << o1 << o2 << o3 or [o1,o2,o3]

  2. define << to behave like an Array

  3. Create a class such as

        class MyObjectCollection < Array
            def &
                # do what I mean
            end
        end
    
  4. 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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜