1

単純な設計目標を実装しようとしていますが、Scala の型システムの複雑さに頭を悩ませています。StreamTraversable、Iterator、Iterable、Stream、View などを比較した後、私の決定はカスタム トレイトを定義することです (簡潔にするために呼び出しましょう)。

  • 非ジェネリックです(私のストリームは意味的にはいくつかの意味しか持たず、Stream[StreamEntry]のような無意味な型を避けたいですStream[Int]
  • と同様の使用法がありますIterable
  • take、などのすべてのメンバーは、基本的なものではなく、drop返す必要があります。StreamIterable

これは私がこれまでに試したことです:

アプローチ1

ユース ケースを概説すると、簡単な例 (3 番目の設計目標に違反します) は次のようになります。

case class StreamEntry(data: Double) // just a dummy

trait Stream extends Iterable[StreamEntry] {
  val metaInfo: String
}

// example use case
val s = new Stream {
  val metaInfo = "something"
  val iterator = StreamEntry(1) :: StreamEntry(2) :: StreamEntry(3) :: Nil toIterator
}

val t = s.take(1) // unfortunately, this is no longer a Stream

アプローチ 2

この 3 番目の要件では、ベース トレイトの代わりにテンプレート トレイトを使用する必要があります (これが、 SomeCollectionまたはSomeCollectionLikeを指す標準的な用語であることを願っています)。これは、 return に拡張するのと同じように、表現するコレクションの戻り値の型を再定義する whichを使用する必要があることを意味します。私の考えは、ほとんど同じことをすることでした。これは:IterableLike[StreamEntry, Stream]IterableIterableLike[A, Iterable[A]]IterableIterable

// this is exactly the way `Iterable` is defined, but non-generic
trait Stream extends Traversable[StreamEntry]
             with GenIterable[StreamEntry]
             with GenericTraversableTemplate[StreamEntry, Stream]
             with IterableLike[StreamEntry, Stream] {
  ...
}

残念ながら、これはコンパイルされません。これは、 のStreamテンプレート引数として表示されGenericTraversableTemplate、コンパイラがテンプレート引数 (正確に 1 つ) を必要とするためStreamです。これは理にかなっています。

アプローチ 3、4、...

ここから、型システムに迷い込みました。削除するだけでは、 fromとの型パラメータの競合により、 のwith GenericTraversableTemplate型が非互換になり、不正な継承が行われます。newBuilderGenericTraversableTemplateGenInterableTraversable

おそらく、最も近い解決策は次のとおりです。

trait Stream extends TraversableLike[StreamEntry, Stream] 
             with IterableLike[StreamEntry, Stream] {
  val metaInfo: String
  def seq = this
  def newBuilder: scala.collection.mutable.Builder[StreamEntry, Stream] = ???
}

これはコンパイルされますが、残念ながらビルダーを実装する方法がわかりません。非ジェネリック トレイトにジェネリック Builder を再利用することはできますか? Stream実際には、他のコレクションから新しいものを実際に構築したくないので、ビルダーなしで行くことができます。しかし、現在、このアプローチで奇妙なランタイム動作が発生しており、完全には理解できません。例えば:

val s = new Stream {
  val metaInfo = "something"
  val iterator = StreamEntry(1) :: StreamEntry(2) :: StreamEntry(3) :: Nil toIterator
}

// executing the following independently (not in sequence) results in:

s.take(1)    // throws: scala.NotImplementedError: an implementation is missing
             // seems to require a Builder :(
s.toArray    // works
s.toIterator // works
s.toIterable // throws: java.lang.ClassCastException: cannot be cast to scala.collection.Iterable

今、私は Scala 型システムの奥深さにいくぶん迷っています。この最後のアプローチで私はまだ正しい軌道に乗っていますか?ビルダーはこのパズルの欠けているピースにすぎませんか?

また、ビルダーの実装は、非ジェネリックで非キャッシュ型の場合、どのように見えるでしょうか? 実装する簡単なアイデアは+=、変更可能なバッファーを使用することですが、これはそもそもイテレーターの使用に非常に反します...そして、toそのクラスを構築する方法がわからない場合、メンバーをどのように実装する必要がありますか?タイプ?関連するコードはすべてライブラリのどこかにあるはずだと思いますが、それを掘り出すことはできません。

4

1 に答える 1

2

わお!あなたはそこで多くのことが起こっています...

この設計上の問題を解決するために知っておくべき、または考慮すべき点がいくつかあります...

用語:

  • 「テンプレート」とは呼ばず、「ジェネリック」または「パラメータ化された」タイプと呼びます。これは、これらのタイプがテンプレートではないためです。つまり、使用されるたびに新しいクラスを作成するために実際の型パラメーターが入力されるわけではありません (「テンプレート」という用語を正しく使用する C++ の場合と同様)。代わりに、1 つのクラスのみが作成され (*)、特定の型パラメーターを持つそのジェネリック型のすべてのインスタンス化が提供されます。

デザインと言語の要素:

あなたは言う:

… 非ジェネリックです (私のストリームは意味的に Stream[StreamEntry] としてのみ意味があり、Stream[Int] のような無意味な型は避けたい)

この要件は、非ジェネリック クラスを主張していません。むしろ、それは「タイプバウンド」の本質です。例えば:

class Generic[T <: UpperBound](ctorArgs...) {
}

この場合、クラスGenericは のサブタイプである型でのみインスタンス化できますUpperBound。(「サブタイプ」と言うときはいつでも、再帰的なサブタイプの関係を意味することに注意してください。つまり、すべてのタイプは、この定義の下ではそれ自体のサブタイプです。

結果:

Scala 標準ライブラリの既存の型によって満たされていない「ストリーム」クラスは何ですか? お気づきのように、標準ライブラリ コレクション クラスの拡張は完全に簡単ではありませんが、実行可能であることは確かです。そうすることは、Scala プログラミングの初歩的な演習ではないと思います。おそらく、Scala への最初の進出の 1 つとして試みるべきではありません。

(*) これは、この説明の目的には十分な単純化です。

于 2013-02-11T15:36:43.237 に答える