Scala:指定默认泛型类型而不是Nothing

时间:2012-08-04 19:05:33

标签: scala generics

我有一对看起来像这样的类。有Generator根据某些类级值生成值,GeneratorFactory构造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接受S类型的参数,Generator.generate生成S类型的值,但没有S类型的值由Generator存储。

我们可以使用这样的类。工厂按Char序列工作,generate生成String因为build获得了String

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的类型为Generator[Char,Nothing]而Scala“无法根据类型为Nothing的集合构造类型为Char的元素类型的集合。”

我想要的是告诉Scala S的“默认值”类似于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

我考虑过度重载Generator.apply以获得一个通用类型的版本,但这会导致错误,因为显然Scala无法区分两个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]

我认为这将是用户最干净的界面。

我是如何实现这一目标的?

3 个答案:

答案 0 :(得分: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成为一种抽象类型,以使其更加偏离用户的方式,但您也可以将其作为类型参数。

答案 1 :(得分:4)

这并没有直接回答你的主要问题,因为我认为其他人正在处理这个问题。相反,它是对您对类型参数的默认值的请求的响应。

我已经对此进行了一些思考,甚至​​开始编写一个建议进行语言更改以允许它。然而,当我意识到Nothing实际上来自哪里时,我停了下来。它不像我预期的那样“默认值”。我将尝试解释它的来源。

为了将类型分配给类型参数,Scala使用最具体的可能/合法类型。因此,例如,假设您有“A [T](x:T)类”并且您说“new A [Int]”。你直接为T指定了“Int”的值。现在假设你说“new A(4)”。 Scala知道4和T必须具有相同的类型。 4可以在“Int”和“Any”之间的任何位置具有类型。在该类型范围中,“Int”是最具体的类型,因此Scala创建“A [Int]”。现在假设您说“新A [AnyVal]”。现在,您正在寻找最具体的类型T,使得Int&lt;:T&lt ;: Any和AnyVal&lt;:T&lt ;: AnyVal。幸运的是,Int&lt ;: AnyVal&lt ;: Any,所以T可以是AnyVal。

继续,现在假设你有“B级[S&gt;:String&lt ;: AnyRef]”。如果你说“新B”,你将不会得到B [Nothing]。相反,你会发现你得到了一个B [String]。这是因为S被约束为String&lt ;: S&lt ;: AnyRef并且String位于该范围的底部。

所以,你看,对于“C类[R]”,“new C”没有给你一个C [Nothing]因为Nothing是类型参数的某种默认值。相反,你得到一个C [Nothing],因为R可以是最低的东西(如果你没有另外指定,Nothing&lt;:R&lt ;: Any)。

这就是为什么我放弃了我的默认类型参数的想法:我找不到让它变得直观的方法。在这个限制范围的系统中,如何实现低优先级默认值?或者,如果默认优先级是“选择最低类型”逻辑,它是否在有效范围内?我想不出一个至少在某些情况下不会混淆的解决方案。如果可以的话,请告诉我,因为我很感兴趣。

编辑:请注意,对于逆变参数,逻辑是相反的。所以,如果你有“D级[-Q]”而你说“新D”,你会得到一个D [Any]。

答案 2 :(得分:2)

一种选择是将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方法将适当的CanBuildFrom实例传播到Generator构造函数,

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不一样&lt; 2.10.0您不能在同一方法定义中混合视图边界和隐式参数列表,因此我们必须将绑定的S <% Seq[T]转换为其等效的隐式参数S => Seq[T]