【问题标题】:Getting #with_index when inheriting from Array从 Array 继承时获取#with_index
【发布时间】:2017-05-29 22:50:57
【问题描述】:

我希望我的 SomeArray#map 返回 SomeArray 类的“数组”。

class SomeArray < Array
  def map
    SomeArray.new(super)
  end
end

some_array = SomeArray.new(["foo", "bar", "baz"])
p some_array.class #=> SomeArray
p some_array.map { |e| e }.class #=> SomeArray

除了现在我还希望能够使用Enumerator#with_index 实例方法。所以理想情况下,这样的事情会起作用:

some_array.map.with_index { |e, i| e }.class #=> SomeArray

这将如何工作?

我试过了:

class SomeArray < Array
  def map
    SomeArray.new(super)
  end

  def with_index(offset = 0)
    super
  end
end

some_array = SomeArray.new(["foo", "bar", "baz"])
p some_array.class #=> SomeArray
p some_array.map.with_index { |e, i| e }.class #=> no implicit conversion of Enumerator into Integer (TypeError)

但它不起作用。

【问题讨论】:

    标签: ruby oop inheritance


    【解决方案1】:

    主要问题是map 是一个数组方法,而with_index 是一个Enumerator 方法。

    • 数组方法被定义为只调用super,并将输出数组转换为SomeArray
    • 枚举器方法首先使用to_enum 定义,将输出转换为数组,然后再转换为SomeArray

    它可能不是你想做的最好的结构,也不是很有效。这只是一个概念的证明!

    class SomeArray < Array
      # Array methods are overwritten here :
      [:map, :select, :reject].each do |array_method_name|
        define_method array_method_name do |*p, &block|
          SomeArray.new(super(*p, &block).to_a)
        end
      end
    
      # Enumerator methods are defined for SomeArray here :
      [:with_index, :with_object].each do |enum_method_name|
        define_method enum_method_name do |*p, &block|
          SomeArray.new(to_enum.public_send(enum_method_name, *p, &block).to_a)
        end
      end
    end
    
    some_array = SomeArray.new(%w(foo bar baz biz))
    
    p some_array.map { |s| s * 2 }.with_index.select { |_, i| i.even? }
    #=> [["foofoo", 0], ["bazbaz", 2]]
    p some_array.map { |s| s * 2 }.with_index.select { |_, i| i.even? }.class
    #=> SomeArray
    

    【讨论】:

      【解决方案2】:

      我认为这里的问题在于,您将 enumerable 和 array 视为相同,而事实并非如此。

      具体来说,这在地图调用中:SomeArray.new(super)

      我可以重现你的错误:

      [6] pry(main)> Array.new [1].map
      TypeError: no implicit conversion of Enumerator into Integer
      

      现在,当您将块传递给 map 时,它可以工作:

      Array.new([1].map { |x| x })  
      => [1]
      

      但在您的map.with_index 中,您并没有这样做。

      你可以这样做:

      module Foo
        include Enumerable
        def map
          puts "calling my map"
          super
        end
        def with_index
          puts "calling my with_index"
          super
        end
      end
      
      class MyArr < Array
        include Foo
      end
      
      puts MyArr.new([1]).map.with_index { |x, y| [x,y] }
      # calling my map
      # calling my with_index
      # 1
      # 0
      

      你为什么要编写这个只调用super的类,这会引起你的疑问。但是如果您想修改 enumerable 的默认功能,这是一种方法。

      【讨论】:

      • 能否详细说明Array.new [1].mapArray.new([1].map { |x| x }) 的区别?它是否与数组的一个实例有关,包括Enumerable 模块,而一个则没有?因为它们都是数组[1].class #=&gt; ArrayArray.new.class #=&gt; Array所以我很困惑有什么区别。
      • 一般你应该使用[ ]括号,但是Array.new有一些额外的能力(见stackoverflow.com/questions/5324654/…)。如果您创建一个继承自 Array 的 MyArray 类,则需要使用MyArray.new,因为[ ] 将使用标准的 Array 类。由于右关联性(通常不需要括号的规则),Array.new 1.map(&amp;:class)Array.new(1.map(&amp;:class)) 相同,但在任何一种情况下,Array.new 都是多余的。可以去掉,效果还是一样的。
      • Array.new [1].mapArray.new([1].map { |x| x}) 的区别不是括号(可以去掉没有效果)。事实上,[1].map(没有块的映射)返回一个 Enumerable,而 [1].map { |x| x } 返回一个数组。
      猜你喜欢
      • 2012-06-01
      • 1970-01-01
      • 2020-09-22
      • 1970-01-01
      • 2018-10-08
      • 1970-01-01
      • 2012-08-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多