【问题标题】:Scala Generics Type Bounds - Pointing to the Actual TypeScala 泛型类型边界 - 指向实际类型
【发布时间】:2018-08-18 04:19:11
【问题描述】:

我正在使用 Scala 泛型和类型边界来了解其可能的用例。我对一个场景感到困惑。

假设我有一个 trait Combinable

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

我想为 Vector[A] 实现一个隐式定义:

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
}

到目前为止一切都很好,如果我将 Vector 替换为 B 类型上限为GenTraversable,问题就开始了:

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

我只是想让这个方法返回类型 B,但是 self ++ other 失败并出现以下编译错误:

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

【问题讨论】:

  • self reduce other 首先是什么? reduce 需要一个归约函数 (A, A) =&gt; A,并返回一个 A。 self reduce other 应该是什么意思,它怎么可能是 Vector?我无法理解“到目前为止一切都很好” - 它还没有编译?
  • 对不起@AndreyTyukin,我尝试了太多东西,这就是我输入错误的原因,现在编辑。 self ++ other 附加这两个 GenTraversables。而且我不希望它返回 GenTraversable,而是返回 B。我希望我的意图现在很清楚。

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


【解决方案1】:

你可以这样做:

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 类上定义的操作可以使用序列的完整类型。

其次,您需要将self 设为B 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)

【讨论】:

  • 啊,它把所有不自然的东西都转移到了使用地点,在那里必须提供额外的 CBF。好的,如果 OP 可以接受必须为每个联合收割机提供 CBF,这确实是另一种可能性。没有考虑到这一点。不错!
【解决方案2】:

基本上,你不能这样做,因为GenTraversable[A] 没有告诉你任何关于++ 的返回类型的具体信息,尤其是它不能保证它会返回B。

即使您扩展了B &lt;: GenTraversableLike[A, B],您仍然会遇到++ 期望隐式CanBuildFrom[Blah, Blahh, That] 并返回That 的问题。

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

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 没有费心为所有可能类型的GenTraversable 提供Monoid 的通用实例,他们只是在List 和Vector 上实现了Monoid(以及其他一些类) ) 代替,但不适用于GenTraversable (如果我错了,请纠正我)。因此,我不会认为这是一件简单的事情。

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

【讨论】:

  • 感谢您的回答安德烈。是的,这看起来很奇怪,但我明白你的意思。您对非常规命名是正确的,我将其重命名为 Combinable.combine 以防止混淆。
  • “它没有编译”是什么意思?我的答案中的代码没有编译吗?那不是计划。我在尝试缩进时搞砸了吗?它确实在本地编译...
  • 原来是 IntelliJ 错误,编译正常。不用担心。
  • @Feyyaz 我已经更新了答案以匹配问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-05
  • 1970-01-01
  • 2013-12-25
  • 1970-01-01
  • 2017-06-07
相关资源
最近更新 更多