在 Scala 中对 collections 进行操作的通用函数

generic functions that operates on collections in scala

这似乎是一个简单的问题,我确信以前有人问过它,但找不到我要找的东西。

如何编写一个以 collection 作为参数(或任何可以被视为 collection 的参数)的函数,对其执行一些操作,然后 return一个collection同类型的?

例如:

scala> def foo[Repr <% Traversable[String]](repr: Repr) = repr.map(_.size)
foo: [Repr](repr: Repr)(implicit evidence: Repr => Traversable[String])Traversable[Int]

这在某些 collection 上工作正常:

scala> foo(Vector("Hello","World"))
res0: Traversable[Int] = Vector(5, 5)

但当我尝试其他 collections(例如 Option)时感到惊讶:

scala> foo(Some("HelloWorld"))
res1: Traversable[Int] = List(10)

一个小问题是 return 类型 Traversable,理想情况下,它是提供给方法的任何类型。更大的问题是实际的实现类型:Option 变成了 List。 更糟糕的是,当在 类 上尝试时(其行为类似于 collections)但没有隐含在它们的范围内。例如:Try:

scala> import scala.util._
import scala.util._

scala> foo(Success("HelloWorld"))
<console>:12: error: No implicit view available from scala.util.Success[String] => Traversable[String].
              foo(Success("HelloWorld"))
                 ^

那么,有没有一种方法可以编写一个通用函数,当给定一个“collection 类”参数时,可以对其元素和 return 正确的类型进行操作?

理想情况下,我想将它用在任何东西上(甚至 FutureTry),但对于我的特定用途,我可以只使用真正的 collections & Option.

编辑:

为了说明一个可能的解决方案(这迫使我复制和粘贴代码,因此,这不是我要找的)是简单地编写两个没有视图边界的函数:

scala> :paste
// Entering paste mode (ctrl-D to finish)

def foo[Repr <: Traversable[String]](repr: Repr) = repr.map(_.size)
def foo(repr: Option[String]) = repr.map(_.size)

// Exiting paste mode, now interpreting.

foo: [Repr <: Traversable[String]](repr: Repr)Traversable[Int] <and> (repr: Option[String])Option[Int]
foo: [Repr <: Traversable[String]](repr: Repr)Traversable[Int] <and> (repr: Option[String])Option[Int]

scala> foo(Vector("bar"))
res2: Traversable[Int] = Vector(3)

scala> foo(Some("bar"))
res3: Option[Int] = Some(3)

映射的概念用函子来表示。为 common 类 轻松提供仿函数实现的一种方法是使用 scalaz 库:

import scala.language.higherKinds
import scalaz.Functor
import scalaz.Scalaz._

def foo[El <: String, Coll[_]](repr: Coll[El])(implicit ev: Functor[Coll]) =
    repr.map(_.size)

现在,这仅适用于 ListVectorFuture

scala> foo(Vector("Hello","World"))
res1: scala.collection.immutable.Vector[Int] = Vector(5, 5)

scala> foo(List("Hello","World"))
res2: List[Int] = List(5, 5)

scala> import scala.concurrent.Future
scala> import scala.concurrent.ExecutionContext.Implicits.global
scala> foo(Future("HelloWorld")) andThen PartialFunction(println(_))

Success(10)

Some 一起使用有点问题,因为只有 OptionFunctor 实现,而不是 Some:

scala> foo(Some("HelloWorld"))
<console>:12: error: could not find implicit value for parameter ev: scalaz.Functor[Some]
              foo(Some("HelloWorld"))
                 ^

所以你必须提供 Option 而不是 Somefoo:

scala> foo(Some("HelloWorld"): Option[String])
res3: Option[Int] = Some(10)

scala> foo(Option("HelloWorld"))
res4: Option[Int] = Some(10)

scala> foo("HelloWorld".some) // This is from scalaz
res5: Option[Int] = Some(10)

并且 scalaz 没有 Try 的任何类型类实现,因此如果您想将 FunctorTry 一起使用,您必须自己提供实现:

import scala.util.Try
import scalaz.Functor

implicit object TryIsFunctor extends Functor[Try] {
  def map[A, B](fa: Try[A])(f: A => B): Try[B] = fa map f
}

那么 foo 将与 Try 一起工作,但类似于 Option,参数的类型应该是 Try,而不是 SuccessFailure:

scala> foo(Try("HelloWorld"))
res9: scala.util.Try[Int] = Success(10)

此外,我相信,在 scalaz 中没有 Functor 实现更通用的集合类型,例如 IterableSeq

在常见的高阶函数中Functor只支持map。所以要使用 flatMapfilter 你必须提供不同的类型 类 而不是 Functor。例如scalaz.Monad支持mapflatMapscalaz.MonadPlus支持mapflatMapfilter

如果您不想使用 scalaz,您可能必须自己制作一些与类型 类 非常相似的东西,以获得更好的结果类型而不是 Traversable。例如,使用标准库中的 CanBuildFrom

我确实认为 Kolmar 对一般问题的看法是正确的,但 Scala 确实支持 duck-typing,因此您可以这样做:

def foo[T[V]](duck: {def map[U](value: String=>U): T[_]}) ={
   duck.map(_.size)
}

foo(Vector("bar")).toVector                         //> res0: Vector[_] = List(3)
foo(Some("bar"))                                  //> res1: Option[_] = Some(3)

(toVector 只是为了强制对否则产生的迭代器求值)