这似乎是一个简单的问题,我以前肯定会被问到,但无法找到我想要的东西。
如何编写一个将集合作为参数的函数(或任何可以作为集合处理的函数),对它执行一些操作,并返回相同类型的集合?
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;参数,可以对它的元素进行操作并返回正确的类型吗?
理想情况下,我想在任何事情上使用它(即使Future
和Try
),但对于我的具体用法,我只能使用真正的收藏品和放大器。 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)
答案 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)
现在,这适用于List
,Vector
和Future
:
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)
实现更多常规集合类型,例如Functor
或Iterable
。
常见的高阶函数Seq
仅支持Functor
。因此,要使用map
和flatMap
,您必须提供不同的类型而不是filter
。例如,Functor
支持scalaz.Monad
和map
,flatMap
支持scalaz.MonadPlus
,map
和flatMap
。
如果您不想使用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,否则会导致)