【问题标题】:generic functions that operates on collections in scala在 scala 中对集合进行操作的通用函数
【发布时间】:2015-09-15 14:45:15
【问题描述】:

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

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

例如:

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"))
                 ^

那么,有没有办法编写一个通用函数,当给定一个“类似集合”的参数时,可以对其元素进行操作并返回正确的类型?

理想情况下,我想在任何东西上使用它(甚至是 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)

【问题讨论】:

  • 并不是所有的集合都可以支持所有可能的操作,所以一般来说你不能写一些东西来支持任意操作。您需要的操作种类是否有任何限制?
  • 具体来说,我只有一个map 调用。但是如果有办法让它更通用并允许其他操作,如flatMapfilter,我想知道如何。
  • 我:我真的不知道你在追求什么。同样,并非所有集合都支持所有操作。所以你需要以某种方式限制类型。您是否要求能够定义一个适用于所有支持flatMap 的集合类型的操作?还是别的什么?
  • 也就是说,“任何可以被视为集合”中的“被视为集合”是什么意思?
  • 您认为哪种实现返回类型适合 foo(Option[T]) ?

标签: scala collections


【解决方案1】:

我确实认为 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 否则会导致)

【讨论】:

  • 太酷了!但返回类型和实际实例并不总是按预期工作。 foo(Some("bar")) 产生 Option[Any] 而不是 Option[Int]foo(Vector("bar")) 产生由迭代器支持的 TraversableOnce[Any] 而不是 Vector[Int]
  • 不过,您展示的内容很酷,谢谢。我开始认为这可能是我所追求的不可能,或者列表并不像我想的那么简单......
  • 请注意,参数需要有一个具有完全相同签名的map 方法,但并非所有可以想到的map 方法都是如此。它适用于Option,因为它的map 方法具有确切的签名。它也适用于任何TraversableOnce,即使对于大多数——例如List——map 方法没有相同的签名。它在这里仍然有效,因为它隐式转换为TraversableOnce.MonadOps,其中map 方法确实具有所需的签名。现在,您可以找到它不起作用的情况,例如 Future
  • @RégisJean-Gilles,感谢您的意见,我同意。但我认为 OP 的问题还要求 map 方法具有完全相同的签名(据我了解他的问题,我认为我不完全这样做)
  • 嗯,不,OP 的问题不一定要求 map 方法具有完全相同的签名,正如 Kolmar 提出的基于类型类的解决方案所证明的那样(其中不同类型的类实例可以在后台调用任何map 方法,甚至可以调用任何方法,无论其名称如何)。此外,map 在 scala 中具有特殊支持,因为它在执行理解时由编译器透明地调用,在这种情况下,map 方法的签名并不重要,只要它可以用单个参数。
【解决方案2】:

映射的概念由函子表示。为通用类轻松提供函子实现的一种方法是使用 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

【讨论】:

  • 谢谢。因为 scalaz 是......好吧...... scalaz,我不想将它添加到项目中。我承认我对它不太熟悉,但我知道添加它可能会导致编译时间急剧增加。你能给我指出如何使用CanBuildFrom 来达到类似结果的正确方向吗?
  • 导入所有 scalaz 将显着增加那些导入它的文件的编译时间(如果它们使用隐式),但如果您将自己限制为您需要的特定导入,那么影响应该与 @ 没有什么不同987654364@。请注意,您可以根据 scalaz-outlaws 获得 Functor[Try] 实例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-09
  • 1970-01-01
  • 1970-01-01
  • 2016-07-16
  • 1970-01-01
  • 2016-05-17
  • 2016-08-28
相关资源
最近更新 更多