Scala:指定一个默认的泛型类型而不是Nothing [英] Scala: specify a default generic type instead of Nothing

查看:85
本文介绍了Scala:指定一个默认的泛型类型而不是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 产生一个字符串 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆