在scala中对集合进行操作的泛型函数

时间:2015-06-29 07:59:27

标签: scala collections

这似乎是一个简单的问题,我以前肯定会被问到,但无法找到我想要的东西。

如何编写一个将集合作为参数的函数(或任何可以作为集合处理的函数),对它执行一些操作,并返回相同类型的集合?

e.g:

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

这对某些收藏品有效:

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

但是当我尝试使用其他集合时感到很惊讶(例如Option):

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

一个小问题是返回类型Traversable,理想情况下,它是给予方法的任何类型。更大的问题是实际的实现类型:Option成为List。 更糟糕的是,当尝试类(行为类似于集合)但没有隐含的范围。例如: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"))
                 ^

那么,有没有办法,编写一个通用函数,当给出一个&#34;集合,如&#34;参数,可以对它的元素进行操作并返回正确的类型吗?

理想情况下,我想在任何事情上使用它(即使FutureTry),但对于我的具体用法,我只能使用真正的收藏品和放大器。 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)

2 个答案:

答案 0 :(得分:2)

映射的概念由仿函数表示。为公共类轻松提供functor实现的一种方法是使用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一起使用是一个问题,因为只有Option实施Functor,而不是Some

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

因此,您必须向Option提供Some而不是foo

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的任何类型类实现,所以如果你想Functor使用Try,你必须自己提供实现:

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,而不是Success或{{1 }}:

Failure

另外,我相信,scalaz中没有scala> foo(Try("HelloWorld")) res9: scala.util.Try[Int] = Success(10) 实现更多常规集合类型,例如FunctorIterable

常见的高阶函数Seq仅支持Functor。因此,要使用mapflatMap,您必须提供不同的类型而不是filter。例如,Functor支持scalaz.MonadmapflatMap支持scalaz.MonadPlusmapflatMap

如果您不想使用scalaz,那么您可能必须使用类型类来创建非常类似的东西,以获得良好的结果类型而不是filter。例如,使用标准库中的Traversable

答案 1 :(得分:2)

我认为Kolmar对于一般问题是对的,但Scala确实支持鸭子打字,所以你可以这样做:

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

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

(toVector只是强制迭代器的eval,否则会导致)

相关问题