Scala:指定一个默认的泛型类型而不是Nothing [英] Scala: specify a default generic type instead of Nothing
问题描述
我有一对看起来像这样的类。有一个 Generator
,它根据一些类级别的值生成一个值,一个 GeneratorFactory
构造一个发电机
。
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生成器[T,S](seq(0),seq(1),seq(2))生成器函数[T](){
def build [S <%Seq [T]](seq:S) )
}
您会注意到 GeneratorFactory.build
接受 S
和 Generator.generate
类型的参数,产生一个<$ c类型的值$ c> S ,但没有任何类型 S
由 Generator
存储。
我们可以使用这样的类。工厂工作在 Char
序列上, generate
产生一个字符串$ c因为
build 给出了一个
字符串
。
这很好,隐式处理 String
类型,因为我们正在使用 GeneratorFactory
。
问题
现在问题出现在我想要的时候在不经过工厂的情况下构建 Generator
。我希望能够做到这一点:
val g2 =生成器('a','b','c' )
g2.generate //错误
但是我得到一个错误,因为 g2
具有类型生成器[Char,Nothing]
和Scala无法使用基于集合的Char类型元素构造Nothing类型的集合的类型。
我想要的是告诉Scala S
的默认值就像 Seq [T]
,而不是 Nothing
。借用缺省参数的语法,我们可以把它看作是类似的:
case class Generator [T,S = Seq [T]]
解决方案不足
当然,如果我们明确地告诉生成器它生成的类型应该是什么类型,但我认为默认选项会更好一些(我的实际场景更复杂):
val g3 =生成器[字符串]('a','b','c')
val o3 = g3generate //正常工作,o3的类型为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 =发生器('a','b','c') //错误:对重载定义的模糊引用
输出
我只想简单地构造 Generator
而不指定类型 > S
,并且默认为 Seq [T]
,这样我就可以做到:
val g2 =生成器('a','b','c')
val o2 = g2.generate
// o2类型为Seq [ Char]
我认为这将是用户最干净的界面。
任何想法,我可以做到这一点吗?
t想要使用基础特征,然后根据需要在其子类中缩小 S
?例如下面的例子符合你的要求:
import scala.collection.generic.CanBuildFrom
trait Generator [T] {
type S
def a:T; def b:T; def c:T
def generate(隐式bf:CanBuildFrom [S,T,S]):S = bf()+ =(a,b,c)结果
}
对象生成器{
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
是一种抽象类型,它使得它更多地偏离用户的方式,但您也可以将它作为类型参数。
I have a pair of classes that look something like this. There's a Generator
that generates a value based on some class-level values, and a GeneratorFactory
that constructs 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))
}
You'll notice that GeneratorFactory.build
accepts an argument of type S
and Generator.generate
produces a value of type S
, but there is nothing of type S
stored by the Generator
.
We can use the classes like this. The factory works on a sequence of Char
, and generate
produces a String
because build
is given a String
.
val gb = GeneratorFactory[Char]()
val g = gb.build("this string")
val o = g.generate
This is fine and handles the String
type implicitly because we are using the GeneratorFactory
.
The Problem
Now the problem arises when I want to construct a Generator
without going through the factory. I would like to be able to do this:
val g2 = Generator('a', 'b', 'c')
g2.generate // error
But I get an error because g2
has type Generator[Char,Nothing]
and Scala "Cannot construct a collection of type Nothing with elements of type Char based on a collection of type Nothing."
What I want is a way to tell Scala that the "default value" of S
is something like Seq[T]
instead of Nothing
. Borrowing from the syntax for default parameters, we could think of this as being something like:
case class Generator[T, S=Seq[T]]
Insufficient Solutions
Of course it works if we explicitly tell the generator what its generated type should be, but I think a default option would be nicer (my actual scenario is more complex):
val g3 = Generator[Char, String]('a', 'b', 'c')
val o3 = g3.generate // works fine, o3 has type String
I thought about overloading Generator.apply
to have a one-generic-type version, but this causes an error since apparently Scala can't distinguish between the two apply
definitions:
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
Desired Output
What I would like is a way to simply construct a Generator
without specifying the type S
and have it default to Seq[T]
so that I can do:
val g2 = Generator('a', 'b', 'c')
val o2 = g2.generate
// o2 is of type Seq[Char]
I think that this would be the cleanest interface for the user.
Any ideas how I can make this happen?
Is there a reason you don't want to use a base trait and then narrow S
as needed in its subclasses? The following for example fits your requirements:
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]
}
}
I've made S
an abstract type to keep it a little more out of the way of the user, but you could just as well make it a type parameter.
这篇关于Scala:指定一个默认的泛型类型而不是Nothing的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!