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

Lui*_*hys 19 generics collections scala

假设我们想要创建一个像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)
Run Code Online (Sandbox Code Playgroud)

到目前为止,这么好,除了我们有一个Traversable后退而不是我们的初始List.

所以我尝试将签名更改为

def multiMinBy[A, B: Ordering, C <: Traversable[A]](xs: C)(f: A => B)
Run Code Online (Sandbox Code Playgroud)

希望我可能得到一个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]]
Run Code Online (Sandbox Code Playgroud)

我认为这是因为我们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]
}
Run Code Online (Sandbox Code Playgroud)

这是有效的,除了我们必须这样称呼它:

multiMinBy((x: String) => x.last)(List("zza","zzza","zzb","zzzb"))
Run Code Online (Sandbox Code Playgroud)

有没有办法保留原始语法,同时获得正确的集合类型?

Dan*_*ral 23

我认为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)
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,我同意,这是一个比我更好的解决方案. (3认同)

Mar*_*amy 13

怎么用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)
Run Code Online (Sandbox Code Playgroud)


Mil*_*bin 10

你的问题是,当作为方法被查看时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)
  }
Run Code Online (Sandbox Code Playgroud)

结构类型HomFilter允许我们声明参数multiMinBy必须具有filter所需结果类型的方法.

示例REPL会话,

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

请记住,这是一个比容器更严格的要求Traversable:允许子类型GenTraversablefilter这种方式定义不规则的方法.上面的签名将静态地阻止这些类型的值传递给multiMinBy... ...可能是你所追求的行为.