我有一对看起来像这样的类。有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]
我认为这将是用户最干净的界面。
我是如何实现这一目标的?
答案 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]
。