在泛型方法中返回原始集合类型

Posted

技术标签:

【中文标题】在泛型方法中返回原始集合类型【英文标题】:Returning original collection type in generic method 【发布时间】:2011-11-22 23:38:05 【问题描述】:

假设我们想要创建一个类似minBy 的函数,它返回集合中所有同等极简主义的元素:

def multiMinBy[A, B: Ordering](xs: Traversable[A])(f: A => B) = 
  val minVal = f(xs minBy f)
  xs filter (f(_) == minVal)


scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last)
res33: Traversable[java.lang.String] = List(zza, zzza)

到目前为止,一切都很好,除了我们有一个Traversable 而不是我们最初的List

所以我尝试将签名更改为

def multiMinBy[A, B: Ordering, C <: Traversable[A]](xs: C)(f: A => B)

希望我能收到C 而不是Traversable[A]。但是,我没有得到任何回报:

scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last)

<console>:9: error: inferred type arguments [Nothing,Nothing,List[java.lang.String]] 
do not conform to method multiMinBy's type parameter bounds [A,B,C <: Traversable[A]]

我认为这是因为C 出现在A 被推断之前的参数中?所以我颠倒了参数的顺序,并添加了一个演员:

def multiMinBy[A, B: Ordering, C <: Traversable[A]](f: A => B)(xs: C) = 
  val minVal = f(xs minBy f)
  (xs filter (f(_) == minVal)).asInstanceOf[C]

这行得通,但我们必须这样称呼它:

multiMinBy((x: String) => x.last)(List("zza","zzza","zzb","zzzb"))

有没有办法在保留原始语法的同时恢复正确的集合类型?

【问题讨论】:

【参考方案1】:

我认为 Miles Sabin 的解决方案太复杂了。 Scala 的集合已经有了使其工作所需的机制,只需进行很小的更改:

import scala.collection.TraversableLike
def multiMinBy[A, B: Ordering, C <: Traversable[A]]
              (xs: C with TraversableLike[A, C])
              (f: A => B): C = 
  val minVal = f(xs minBy f)
  xs filter (f(_) == minVal)

【讨论】:

是的,我同意,这是一个比我更好的解决方案。 您能解释一下为什么必须使用 GreaterLowerBound:C with TraversableLike[A, C]。你怎么知道这个方法需要返回 C?我想我不明白输入的特定类型保存在哪里 @Adrian 使用 Scala 2.13,这可能不再适用。事实上,我怀疑它是。 Traversable[A] 绑定的授予方法仅取决于项目的类型 ATraversableLike[A, C] 授予的方法不仅处理类型项目类型A,而且_return 类型C。因为它是C with TraversableLike[A,C],这意味着我可以在它上面调用一个方法,它会返回相同的集合C,而不是,比如说,返回Traversable[A]filter 方法就是这种情况,如果没有这个绑定,它不会返回 C 啊,所以这与联合哦方法(来自 Traversable[A] 和来自 TraversableLike[A, C] 的方法) @Adrian 与其说是方法的联合,不如说是类型的统一。两者都存在相同的方法(过滤器),但TraversableLike 的返回类型更具体。我可能使用了just TraversableLike,但我猜——我不记得了——如果我这样做了,类型推断就不起作用了。【参考方案2】:

CanBuildFrom怎么样?

import scala.collection.immutable._
import scala.collection.generic._

def multiMinBy[A, B, From[X] <: Traversable[X], To](xs: From[A])(f: A => B)
  (implicit ord: Ordering[B], bf: CanBuildFrom[From[_], A, To])  = 
  val minVal = f(xs minBy f)
  val b = bf()
  b ++= (xs filter (f(_) == minVal))
  b.result
 



scala> multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last)
res1: List[java.lang.String] = List(zza, zzza)

【讨论】:

【参考方案3】:

您的问题是,当被视为GenTraversable[A] 上的方法时(我将在此答案中使用它而不是Traversable[A]filter 方法的结果类型并不比GenTraversable[A] 更精确。不幸的是,在编写的 multiMinBy 方法的主体中,这就是你所知道的关于 xs 的全部内容。

要获得您所追求的结果,您必须使multiMinBy 的签名更加精确。在保持容器类型相对开放的同时执行此操作的一种方法是使用如下结构类型,

type HomFilter[CC[X] <: GenTraversable[X], A] = 
  CC[A]  def filter(p : A => Boolean) : CC[A] 

def multiMinBy[CC[X] <: GenTraversable[X], A, B: Ordering]
  (xs: HomFilter[CC, A])(f: A => B) : CC[A] = 
    val minVal = f(xs minBy f)
    xs filter (f(_) == minVal)
  

结构类型HomFilter 允许我们断言multiMinBy 的参数必须具有具有所需结果类型的filter 方法。

REPL 会话示例,

scala> val mmb = multiMinBy(List("zza","zzza","zzb","zzzb"))(_.last)
mmb: List[String] = List(zza, zzza)

请记住,这是一个比容器只是 Traversable 更严格的要求:允许 GenTraversable 的子类型以这种方式定义不规则的 filter 方法。上面的签名将静态地阻止此类类型的值被传递给multiMinBy ...大概这就是您所追求的行为。

【讨论】:

只需使用TraversableLike,就可以避免整个结构类型的事情。

以上是关于在泛型方法中返回原始集合类型的主要内容,如果未能解决你的问题,请参考以下文章

在泛型方法中返回特定类型,具体取决于运行时没有反射或动态的枚举值

如何在泛型集合上创建扩展方法

java学习笔记9.22(泛型)

如何添加类型约束以在泛型方法中包含任何可序列化的内容?

通过在运行时在泛型方法中进行类型转换来使用递归函数

无法在泛型方法中将类型更改为可为空