【问题标题】:Return a generic Traversable of a specified type返回指定类型的通用 Traversable
【发布时间】:2019-10-20 10:15:38
【问题描述】:

我希望能够对T[_] <: Traversable 之类的类型进行通用操作,以便我可以执行映射和过滤等操作,但我想尽可能推迟决定选择哪个Traversable .

我希望能够针对通用T[Int] 编写函数,返回T[Int] 而不是Traversable[Int]。因此,例如,我想将一个函数应用于 Set[Int]Vector[Int] 或任何扩展 Traversable 并取回该类型的东西。

我首先尝试以一种简单的方式来做到这一点,例如:

trait CollectionHolder[T[_] <: Traversable[_]] {

  def easyLessThanTen(xs: T[Int]): T[Int] = {
    xs.filter(_ < 10)
  }
}

但这不会编译:缺少扩展函数的参数类型。但是,如果函数采用Traversable[Int] 而不是T[Int],它将编译,所以我认为我可以使用Traversable 并转换为T。这将我引向CanBuildFrom

object DoingThingsWithTypes {    

  trait CollectionHolder[T[_] <: Traversable[_]] {

    def lessThanTen(xs: T[Int])(implicit cbf: CanBuildFrom[Traversable[Int], Int, T[Int]]): T[Int] = {

      val filteredTraversable = xs.asInstanceOf[Traversable[Int]].filter(_ < 10)

      (cbf() ++= filteredTraversable).result
}

编译。但是在我的测试中:

val xs = Set(1, 2, 3, 4, 1000)

object withSet extends CollectionHolder[Set]

withSet.lessThanTen(xs) shouldBe Set(1, 2, 3, 4)

我收到以下编译器错误:

无法构造带有类型元素的 Set[Int] 类型的集合 Int 基于 Traversable[Int] 类型的集合。不够 方法lessThanTen的参数:(隐式cbf: scala.collection.generic.CanBuildFrom[Traversable[Int],Int,Set[Int]])Set[Int]。 未指定值参数cbf。

我在哪里可以获得 CanBuildFrom 来进行此转换?或者更好的是,我怎样才能修改我更简单的方法以获得我想要的结果?或者我是否需要使用类型类并为我感兴趣的每个 Traversable 编写一个隐式实现(一个用于 Set,一个用于 Vector 等)?如果可能的话,我宁愿避免使用最后一种方法。

【问题讨论】:

  • 我建议使用更高的抽象,例如Functor typeclass
  • 你能稍微扩展一下这个想法吗?
  • 我的意思是,而不是尝试使用集合库和构建以及所有这些。您可以只使用Functor.map 之类的方式编写代码。如果你没有这方面的经验,你可以从阅读 scala-with-cats 开始。此外,如果您可以提供一个包含您需要的所有方法的具体示例,我将能够提供一个实现。
  • 最终的实现将有相当多的方法使用诸如:filter、exists 和 nonEmpty(因此使用 Traversable)。如果我需要为我将使用的每个 Traversable(Set、Vector 等)编写一个新的类型类,那么对于每个函数,我似乎不妨将它们保留为抽象并为每个可遍历实现所有内容。我希望大部分函数都建立在少量抽象函数之上,所以为新类型实现这个只意味着编写少量函数,其余的都是免费的。

标签: scala generics scala-collections type-parameter


【解决方案1】:

使用 (Scala 2.12.8) 标准库而不是 cat/scalaz/etc。你需要看看GenericTraversableTemplatefilter 没有在那里定义,但可以很容易地定义:

import scala.collection.GenTraversable
import scala.collection.generic.GenericTraversableTemplate

trait CollectionHolder[T[A] <: GenTraversable[A] with GenericTraversableTemplate[A, T]] {

  def lessThanTen(xs: T[Int]): T[Int] = {
    filter(xs)(_ < 10)
  }

  def filter[A](xs: T[A])(pred: A => Boolean) = {
    val builder = xs.genericBuilder[A]
    xs.foreach(x => if (pred(x)) { builder += x })
    builder.result()
  }
}

在你提到nonEmptyexists的评论中;由于GenTraversable 类型绑定,它们可用。真的filter 也是,问题是它返回GenTraversable[A] 而不是T[A]

Scala 2.13 对集合进行了重新设计,因此那里的方法可能会略有不同,但我还没有看够。

另外:T[_] &lt;: Traversable[_] 可能不是您想要的,而不是 T[A] &lt;: Traversable[A];例如如果您有T[Int] &lt;: Traversable[String],则不会违反第一个约束。

【讨论】:

    【解决方案2】:

    是的,我是说你应该使用 typeclasses
    但是,您不必实现它们,也不必为您需要的类型提供它们的实例。因为,这些非常常见,可以在 中找到,例如 catsscalaz

    例如,使用cats:

    import cats.{Traverse, TraverseFilter}
    import cats.syntax.all._ // Provides the nonEmpty, filter & map extension methods to C.
    
    import scala.language.higherKinds
    
    def algorithm[C[_]: TraverseFilter: Traverse](col: C[Int]): C[Int] =
      if (col.nonEmpty)
        col.filter(x => x < 10)
      else
        col.map(x => x * 2) // nonsense, but just to show that you can use map too.
    

    你可以这样使用:

    import cats.instances.list._
    
    algorithm(List(1, 200, 3, 100))
    // res: List[Int] = List(1, 3)
    

    值得补充的是,还有很多其他方法,例如existsfoldLeftsize 等。
    看看documentation。如果这是您第一次使用catsscalaz 或这些概念,您可能会发现scala-with-cats 非常有启发性。

    【讨论】:

    • TraverseFilter 看起来和我要找的完全一样,谢谢。我已经在使用一些 Cats 类型类(Monad、Functor)。你能帮我理解这里的语法吗:“C[_]:TraverseFilter:Traverse”。它看起来像一个类型参数,其类型也有类型?我想我以前没见过这个。
    • def algo[C[_]: TraverseFilter: Traverse] 等价于def algo[C[_]](implicit ev1: TraverseFilter[C], ev2: Traverse[C])。所以基本上,它要求隐式参数。更具体地说,给定类型参数的 typeclass 实例 - 它被称为 context bounds
    猜你喜欢
    • 2019-12-14
    • 1970-01-01
    • 2018-08-03
    • 1970-01-01
    • 2018-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-19
    相关资源
    最近更新 更多