Scala Generics Type Bounds - 指向实际类型

时间:2018-03-09 14:54:59

标签: scala generics typeclass type-bounds f-bounded-polymorphism

我正在使用scala泛型和类型边界来理解其可能的用例。我很困惑一个场景。

假设我有一个特性可组合

i

我想为Vector [A]实现隐式def:

trait Combinable[T] {
    def combine(other: T): T
}

到目前为止,一切都很好,如果我将带有B型上限的Vector替换为implicit def vectorCombinable[A](self: Vector[A]) = new Combinable[Vector[A]] { // note: using scala 2.11, no SAM support override def combine(other: Vector[A]): Vector[A] = self ++ other } ,则问题就开始了:

GenTraversable

我只是希望此方法在类型B中返回,但implicit def vectorCombinable[A, B <: GenTraversable[A]](self: B): Combinable[B] = new Combinable[B] { override def combine(other: B): B = self ++ other } 失败并出现以下编译错误:

  

GenTraversable [A]类型的表达式不符合预期类型   乙

2 个答案:

答案 0 :(得分:3)

你可以这样做:

implicit def vectorCombinable[A, B <: GenTraversableLike[A, B]]
  (self: B with GenTraversable[A])
  (implicit cbf: CanBuildFrom[B, A, B])
: Combinable[B] = new Combinable[B] {
  override def combine(other: B): B = self ++ other
}

首先,您需要B来扩展GenTraversableLike,因为scala.collection.???Like类包含其元素的类型和签名中序列的完整类型。例如Vector[Int]扩展GenTraversableLike[Int, Vector[Int]]。因此,???Like类上定义的操作可以使用完整类型的序列。

其次,您需要selfB with GenTraversable[A],因为编译器应该能够从单个签名中找出序列的类型和元素的类型。

第三,您必须提供隐式CanBuildFrom[B, A, B],证明您可以使用序列B中的A类型的元素构建序列B。此证明将提供给++

GenTraversable方法

毕竟,它运作正常:

scala> List(1,2,3).combine(List(4,5,6))
res0: List[Int] = List(1, 2, 3, 4, 5, 6)

scala> Set(1,2,3).combine(Set(4,5,6))
res1: scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 3, 4)

scala> Map(1 -> "a", 2 -> "b").combine(Map(1 -> "c", 3 -> "d"))
res2: scala.collection.immutable.Map[Int,String] = Map(1 -> c, 2 -> b, 3 -> d)

答案 1 :(得分:1)

基本上,您无法执行此操作,因为GenTraversable[A]并未告诉您有关++的返回类型的任何具体信息,尤其是它无法保证它会返回B 1}}。

即使您延长了B <: GenTraversableLike[A, B],您仍会遇到++期望隐式CanBuildFrom[Blah, Blahh, That]并返回That的相同问题。

为了保证您的方法combine返回相同类型的集合,而不依赖于任何外部CanBuildFrom,您可以这样做:

import scala.collection._
import scala.collection.generic.GenericTraversableTemplate
import scala.language.implicitConversions
import scala.language.higherKinds

trait Combinable[T] {
    def combine(other: T): T
}

implicit def genericCombinable
  [A, CC[X] <: 
    GenericTraversableTemplate[X, CC] with 
    GenTraversable[X] with 
    TraversableOnce[X]
  ]
  (c: CC[A])
: Combinable[CC[A]] = {
  new Combinable[CC[A]] {
    override def combine(other: CC[A]): CC[A] = {
      val bldr = c.genericBuilder[A]
      bldr ++= c
      bldr ++= other
      bldr.result
    }
  }
}

现在它编译并使用标准库中的大多数集合,因为它们中的大多数都倾向于实现GenericTraversableTemplate

我建议你不要花太多时间在这上面。例如,scala cats没有为所有可能类型的Monoid提供GenTraversable的通用实例,他们只是在Monoid上实现List {1}}和Vector(以及其他一些课程)相反,但不适用于GenTraversable(如果我错了,请纠正我)。因此,我不认为这是一件简单的事情。

最后一点:由于隐式转换,编译器应该给出警告,这是正确的。