5

コレクションを表すクラスがあります。モジュールをその中に含めてEnumerableメソッドを定義した#eachので、すべてのメソッドを取得できます。

しかし、問題は、Enumerableのメソッドが同じクラスを保持しないことです。したがって、たとえば、私のクラスに名前が付けられている場合、名前が付けられているCollection場合はCollection#select、結果のクラスもCollection(ではなくArray)になります。これを達成する方法はありますか?

4

4 に答える 4

6

は配列を返すように設計されているため、それをインスタンスEnumerable#selectにマップする方法をどこかに指示する必要があります。Collectionつまり、を明示的に定義する必要がありますCollection#select。そうしないと、Rubyは元の配列結果Enumerable#selectからCollectionインスタンスへのマッピングルールを認識しません。

于 2012-10-27T00:04:18.593 に答える
4

残念ながら、Rubyのコレクション操作は型を保持していません。すべての収集操作は常にを返しますArray

Setsやsのようなコレクションの場合Tree、これは単に煩わしいだけです。なぜなら、それらを常に必要なタイプに戻す必要があるからです。しかし、たとえば、すべての素数の無限の遅延ストリームの場合、これは壊滅的です。プログラムがハングするか、無限に大きなを構築しようとしてメモリが不足しますArray

ほとんどのコレクションAPIは、重複するコードを排除するか、型を保持しますが、両方を保持することはできません。たとえば、.NETのコレクションAPIはほとんどの場合重複コードを排除しますが、常に同じタイプを返しますIEnumerable(Rubyと同等Enumerator)。SmalltalkのコレクションAPIはタイプを保持しますが、すべてのコレクションタイプのすべてのコレクション操作を複製することでこれを実現します。

型を保持しながら重複を排除する唯一のCollectionAPIはScalaのものです。これは、特定のタイプのコレクションを効率的に構築する方法を知っているコレクションビルダーの新しい概念を導入することによって実現されます。コレクション操作はコレクションビルダーの観点から実装され、コレクションビルダーのみを複製する必要があります…しかし、それらはとにかくすべてのコレクションに固有です。

Rubyで型を保持するコレクション操作が必要な場合は、独自のコレクション内のすべてのコレクション操作を複製するか(独自のコードに限定されます)、ビルダーを使用するようにコレクションAPI全体を再設計する必要があります(大幅な再設計が必要になります)。独自のコードだけでなく、これまでに作成されたすべてのサードパーティコレクションを含む既存のコレクションも含まれます)。

2番目のアプローチは、不可能ではないにしても、少なくとも実用的ではないことは明らかです。ただし、最初のアプローチにも問題があります。収集操作はsを返すことが期待されArrayており、その期待に違反すると他の人のコードが破損する可能性があります。

Ruby2.0のレイジーコレクション操作と同様のアプローチを取ることができpreserve_typeます。タイプを保持するコレクション操作でプロキシオブジェクトを返す新しいメソッドをAPIに追加できます。このようにして、標準APIからの逸脱がコードで明確に示されます。

c.select …               # always returns an Array

c.preserve_type.select … # returns whatever the type of c is

何かのようなもの:

class Hash
  def preserve_type
    TypePreservingHash.new(self)
  end
end

class TypePreservingHash
  def initialize(original)
    @original = original
  end

  def map(*args, &block)
    Hash[@original.map(*args, &block)
    # You may want to do something more efficient
  end
end
于 2012-10-27T03:26:26.730 に答える
3

別の方法は、コレクションを基になる配列のプロキシにすることです。

class Collection
  def initialize( items= nil )
    @items = items || []
  end

  def respond_to_missing?(method_name, include_private = false)
    Enumerable.instance_methods.include? method_name
  end

  def method_missing name, *args, &block
    if @items.respond_to? name
      res = @items.send name, *args, &block
      res.kind_of?( Array ) ? Collection.new(res) : res
    else
      super
    end
  end
end

IRBで:

col = Collection.new [1,2,3]
=> #<Collection:0x0000010102d5d0 @items=[1, 2, 3]>
col.respond_to? :map
=> true
col.map{|x| x * 2 }
=> #<Collection:0x000001009bff18 @items=[2, 4, 6]>
于 2012-10-27T02:04:16.540 に答える
2

以下は私のために働いた。再定義する必要があるのはフィルタリングメソッドだけでした。を返すすべてのメソッドを再定義する場合、これには再定義してはならないメソッドがArray含まれます。collect

  include Enumerable

  def select(&block)
    self.class.new(super.select(&block))
  end

  def reject(&block)
    self.class.new(super.reject(&block))
  end
于 2020-05-15T15:35:17.227 に答える