13

私はこのようなクラスのペアを持っています。いくつGeneratorかのクラスレベルの値に基づいて値を生成するaと、GeneratorFactoryを構築するaがありGeneratorます。

case class Generator[T, S](a: T, b: T, c: T) {
  def generate(implicit bf: CanBuildFrom[S, T, S]): S =
    bf() += (a, b, c) result
}

case class GeneratorFactory[T]() {
  def build[S <% Seq[T]](seq: S) = Generator[T, S](seq(0), seq(1), seq(2))
}

GeneratorFactory.build型の引数を受け入れ、型の値SGenerator.generate生成することに気付くでしょうが、によって格納される型の値はSありません。SGenerator

このようなクラスを使用できます。ファクトリはのシーケンスで動作し、が与えられているためCharにをgenerate生成します。StringbuildString

val gb = GeneratorFactory[Char]()
val g = gb.build("this string")
val o = g.generate

これは問題なく、Stringを使用しているため、型を暗黙的に処理しGeneratorFactoryます。


問題

Generatorここで、工場を経由せずにを構築したいときに問題が発生します。私はこれを行うことができるようにしたいと思います:

val g2 = Generator('a', 'b', 'c')
g2.generate // error

g2しかし、タイプとScalaがあるため、エラーが発生しますGenerator[Char,Nothing]。「タイプNothingのコレクションに基づいて、タイプCharの要素を使用してタイプNothingのコレクションを構築することはできません。」

私が欲しいのは、の「デフォルト値」がの代わりのSようなものであることをScalaに伝える方法です。デフォルトパラメータの構文から借用すると、これは次のようなものと考えることができます。Seq[T]Nothing

case class Generator[T, S=Seq[T]]

不十分なソリューション

もちろん、生成されたタイプがどうあるべきかをジェネレーターに明示的に指示すれば機能しますが、デフォルトのオプションの方が良いと思います(私の実際のシナリオはより複雑です)。

val g3 = Generator[Char, String]('a', 'b', 'c')
val o3 = g3.generate  // works fine, o3 has type String

オーバーロードを1つのジェネリック型バージョンにすることを考えましたが、Scalaは2つの定義Generator.applyを区別できないため、エラーが発生します。apply

object Generator {
  def apply[T](a: T, b: T, c: T) = new Generator[T, Seq[T]](a, b, c)
}

val g2 = Generator('a', 'b', 'c')  // error: ambiguous reference to overloaded definition

必要な出力

私が欲しいのはGenerator、タイプを指定せずに単純にを構築し、Sそれをデフォルトにして、次のSeq[T]ことができるようにする方法です。

val g2 = Generator('a', 'b', 'c')
val o2 = g2.generate
// o2 is of type Seq[Char]

これがユーザーにとって最もクリーンなインターフェースになると思います。

これを実現する方法はありますか?

4

3 に答える 3

5

S基本特性を使用して、そのサブクラスで必要に応じて絞り込みたくない理由はありますか?たとえば、次の要件に適合します。

import scala.collection.generic.CanBuildFrom

trait Generator[T] {
  type S
  def a: T; def b: T; def c: T
  def generate(implicit bf: CanBuildFrom[S, T, S]): S = bf() += (a, b, c) result
}

object Generator {
  def apply[T](x: T, y: T, z: T) = new Generator[T] {
    type S = Seq[T]
    val (a, b, c) = (x, y, z)
  }
}

case class GeneratorFactory[T]() {
  def build[U <% Seq[T]](seq: U) = new Generator[T] {
    type S = U
    val Seq(a, b, c, _*) = seq: Seq[T]
  }
}

Sユーザーの邪魔にならないように抽象型を作成しましたが、型パラメーターにすることもできます。

于 2012-08-04T20:55:17.620 に答える
4

他の人がそれを扱っていると思うので、これはあなたの主な質問に直接答えることはありません。むしろ、これは型引数のデフォルト値の要求に対する応答です。

私はこれについて少し考えましたが、それを可能にするために言語の変更を開始するための提案を書き始めたところまで行っています。しかし、実際には何も出てこないことに気づいたとき、私は立ち止まりました。思ったような「デフォルト値」ではありません。それがどこから来たのかを説明しようと思います。

型を型引数に割り当てるために、Scalaは最も具体的な可能な/合法的な型を使用します。したがって、たとえば、「クラスA [T](x:T)」があり、「新しいA[Int]」と言うとします。Tに「Int」の値を直接指定しました。ここで「newA(4)」と言ったとします。Scalaは、4とTが同じタイプでなければならないことを知っています。4は、「Int」と「Any」の間の任意のタイプを持つことができます。その型の範囲では、「Int」が最も具体的な型であるため、Scalaは「A[Int]」を作成します。ここで、「新しいA[AnyVal]」と言ったとします。ここで、Int <:T <:AnyおよびAnyVal <:T <:AnyValのような最も具体的なタイプTを探しています。幸い、Int <:AnyVal <:Anyなので、TはAnyValにすることができます。

続けて、「クラスB [S>:文字列<:AnyRef]」があるとします。「新しいB」と言うと、B[なし]は得られません。むしろ、B[String]を取得することがわかります。これは、SがString <:S <:AnyRefとして制約されており、Stringがその範囲の下限にあるためです。

つまり、「class C [R]」の場合、「newC」はC[Nothing]を提供しません。これは、Nothingが型引数のある種のデフォルト値であるためです。むしろ、C [Nothing]を取得します。これは、Rが可能な最低のものであるためです(特に指定しない場合、Nothing <:R <:Any)。

これが、デフォルトの型引数のアイデアをあきらめた理由です。直感的にする方法が見つかりませんでした。範囲を制限するこのシステムでは、優先度の低いデフォルトをどのように実装しますか?または、有効な範囲内にある場合、デフォルトの優先順位は「最も低いタイプを選択する」ロジックですか?少なくともいくつかのケースで混乱しないような解決策は考えられませんでした。興味がありますので、よろしければお知らせください。

編集:反変パラメーターの論理が逆になっていることに注意してください。したがって、「クラスD [-Q]」があり、「新しいD」と言うと、D[Any]が得られます。

于 2012-08-04T22:35:59.380 に答える
2

1つのオプションは、の召喚を、CanBuildFromそれ(またはむしろそのインスタンス)が決定するのに役立つ場所に移動することですS

case class Generator[T,S](a: T, b: T, c: T)(implicit bf: CanBuildFrom[S, T, S]) {
  def generate : S =
    bf() += (a, b, c) result
}

サンプルREPLセッション、

scala> val g2 = Generator('a', 'b', 'c')
g2: Generator[Char,String] = Generator(a,b,c)

scala> g2.generate
res0: String = abc

アップデート

またGeneratorFactory、そのメソッドが適切なインスタンスをコンストラクターにbuild伝播するように、を変更する必要があります。CanBuildFromGenerator

case class GeneratorFactory[T]() {
  def build[S](seq: S)(implicit conv: S => Seq[T], bf: CanBuildFrom[S, T, S]) =
    Generator[T, S](seq(0), seq(1), seq(2))
}

Scala <2.10.0では、ビューの境界と暗黙のパラメーターリストを同じメソッド定義に混在させることはできないため、境界S <% Seq[T]を同等の暗黙のパラメーターに変換する必要がありますS => Seq[T]

于 2012-08-04T21:11:21.610 に答える