【问题标题】:Typeclass and the scala Collection Interface类型类和 scala 集合接口
【发布时间】:2013-04-08 06:41:57
【问题描述】:

我正在尝试实现一个函数,该函数适用于具有mapflatMap 方法的类型。我已经为Traversable 做了它,但这不直接包括FutureOption。所以我决定使用我自己的接口,使用类型类:

trait CanMap[A, M[_]] {
  def map[B](l: M[A])(f: A => B): M[B]
  def flatMap[B](l: M[A])(f: A => M[B]): M[B]
}

我已经为Option实现了这个:

  implicit def canmapopt[A] = new CanMap[A, Option] {
    def map[B](l: Option[A])(f: A => B): Option[B] = l.map(f)
    def flatMap[B](l: Option[A])(f: A => Option[B]): Option[B] = l.flatMap(f)
  }

而且这个效果很好。现在我想为 Traversable 的任何子类型实现它,我尝试了一个非常接近 Option 的实现:

  implicit def canmaptrav[A, B, T[B] <: Traversable[B]] = new CanMap[A, T] {
    def map[B](l: T[A])(f: A => B): T[B] = l.map(f)
    def flatMap[B](l: T[A])(f: A => T[B]): T[B] = l.flatMap(f)
  }

但我得到了错误:

  type mismatch;  found   : Traversable[B]  required: T[B]  Note: implicit method canmaptrav is not applicable here because it comes after the application point and it lacks an explicit result type

对于l.map的返回类型。我不明白为什么l.map(f) 会返回Traversable 而不是特定类型T[B]。所以我试图明确地将正确类型的 CanBuildFrom 放在上下文中:

  implicit def canmaptrav[A, B, T[B] <: Traversable[B]](implicit cbf: CanBuildFrom[T[A], B, T[B]]) = new CanMap[A, T] {
    def map[B](l: T[A])(f: A => B): T[B] = l.map(f)
    def flatMap[B](l: T[A])(f: A => T[B]): T[B] = l.flatMap(f)
  }

错误仍然存​​在。

知道我哪里出错了吗?这可能很明显,但我对我猜的泛型类型签名感到困惑。

更新:解决方案

首先,正如答案所指出的,CanMap 主要是一个 Functor/Monad,所以如果你敢,你可以使用 scalaz 来实现它。但是,如果您像我一样想尝试不使用它,这里是基于 Kipton Barros 的回答的解决方案:

trait CanMap[A, B, M[_]] {
  def map(l: M[A])(f: A => B): M[B]
  def flatMap(l: M[A])(f: A => M[B]): M[B]
}

implicit def canmapopt[A, B] = new CanMap[A, B, Option] {
  def map(l: Option[A])(f: A => B): Option[B] = l.map(f)
  def flatMap(l: Option[A])(f: A => Option[B]): Option[B] = l.flatMap(f)
}

implicit def canmaptrav[A, B, M[+_]](implicit bf: CanBuildFrom[M[A], B, M[B]], ev: M[A] => TraversableLike[A, M[A]], eb: M[B] => TraversableLike[B, M[B]]) = new CanMap[A, B, M] {
  def map(l: M[A])(f: (A) => B): M[B] = l.map(f)
  def flatMap(l: M[A])(f: A => M[B]): M[B] = l.flatMap[B, M[B]] { (a: A) =>
    f(a)
  }
} 

诀窍是使用隐式转换 M[A] =&gt; TraversableLike[A, M[A]] 而不是尝试子类型 Traversable

【问题讨论】:

    标签: scala collections typeclass


    【解决方案1】:

    Scalaz 已经包含这些类型类,它们被称为MonadFunctor。一个简短的例子:

    // map
    def foo[F[_] : Functor](xs: F[Int]) = xs.map(_ + 1)
    
    scala> foo(List(1,2,3))
    res2: List[Int] = List(2, 3, 4)
    
    // flatMap
    def flatten[M[_] : Monad](xs: M[M[Int]]) = xs.flatMap(identity)
    
    scala> flatten(List(List(1,2,3)))
    res3: List[Int] = List(1, 2, 3)
    

    编辑

    Future 的仿函数实例可能如下所示:

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

    【讨论】:

    • 谢谢,确实,scalaz 提供了我需要的一切,但我想在没有整个 scalaz 库的情况下做到这一点。尤其是我看了代码,实在不知道怎么用,连依赖下载都下不了。 Scalaz 看起来很酷,但它太复杂了,无法从获取 Monad 类型类开始。
    【解决方案2】:

    第一个问题是 Traversable map 方法中的“幕后”发生了很多事情。返回最具体的集合类型需要做一些工作,这就是您需要CanBuildFrom 的原因。第二个问题是Option 没有实现Traversable 接口,所以它的map 方法没有采用CanBuildFrom

    这是我能得到的最接近的,

    import scala.collection.generic.CanBuildFrom
    import collection.TraversableLike
    
    trait CanMap[A, M[_]] {
      def map[B](l: M[A])(f: A => B)(implicit bf: CanBuildFrom[M[A], B, M[B]]): M[B]
    }
    
    object Test {
    
      // ugly hack to work around nonexistent CanBuildFrom for Option
      implicit def optionBuilder[A, B]: CanBuildFrom[Option[A], B, Option[B]] = null
    
      implicit def canmapopt[A] = new CanMap[A, Option] {
        def map[B](l: Option[A])(f: A => B)(implicit bf: CanBuildFrom[Option[A], B, Option[B]]): Option[B] = l.map(f)
      }
    
      implicit def canmaptrav[A, M[_]](implicit ev: M[A] => TraversableLike[A, M[A]]) = new CanMap[A, M] {
        def map[B](l: M[A])(f: (A) => B)(implicit bf: CanBuildFrom[M[A], B, M[B]]): M[B] = l.map(f)
      }
    
      // example usage
    
      def mapper[A, B, M[_]](l: M[A])(f: A => B)(implicit cm: CanMap[A,M], bf: CanBuildFrom[M[A], B, M[B]]) = {
        cm.map(l)(f)
      }
      mapper(List(1,2,3))(_ + 1)          // List(2,3,4)
      mapper(Some(2): Option[Int])(_ + 1) // Some(3)
      // (cast to Option[Int] is needed to find the canmapopt implicit)
    }
    

    顺便说一句,到TraversableLike 的隐式转换使得这也适用于数组,

     mapper(Array(1,2,3))(_ + 1)         // Array(2, 3, 4)
    

    【讨论】:

    • 太棒了! TraversableLike 隐式转换很酷:)​​ 我不得不稍微调整您的解决方案,将CanBuildFrom 作为隐式移动到canmaptrav,这样它就不会“毒害”canmapopt。为了能够实现flatMap,我还必须为M[B] 类型添加另一个隐式CanBuildFromcanmaptravB 现在是 CanMap[A, B, M[_]] 中的一个参数,而不再是函数。
    • 很高兴您找到了解决方案。在CanMap 中包含B 类型可以大大简化事情,但缺点是每个CanMap 对象仅映射到一种特定类型。我看着斯卡拉兹。他们完全避免了CanBuildFrom,它有优点(更简单)和缺点(目标类型不那么具体;例如,不会自动使用数组)。
    • 我真的没有 B 必须是特定类型的问题,因为它也是提供 CanMap 的隐式参数。所以它是由 scala 动态具体化的,具体取决于我使用它的位置。
    【解决方案3】:

    首先,我尝试了您两次失败的尝试,但并没有从中受益。然后我决定简单一点,做我自己的 CanMap 实现。我最终得到了这个:

      def canmaptrav[A] = new CanMap[A, Traversable]{
        def map[B](l: Traversable[A])(f: A => B): Traversable[B]= l.map(f)
        def flatMap[B](l: Traversable[A])(f: A => Traversable[B]): Traversable[B] = l.flatMap(f)
      }
    

    正好是CanMap[_,Option]。假设您正在寻找的子类型适用于这样的用例:

    canmaptrav[Int].map(List(1,2,3,4))(_ + 1)       //> res0: Traversable[Int] = List(2, 3, 4, 5)
    canmaptrav[Int].map(Vector(1,2,3,4))(_ + 1)     //> res1: Traversable[Int] = Vector(2, 3, 4, 5)
    

    现在,如果您希望 res1 和 res0 类型成为具体类型(List、Vector),那么该方法将不得不依赖 CanBuildFrom from。

    顺便说一句,你知道CanMap 几乎就是 Monad 接口吧?

    【讨论】:

    • 是的,我已经做到了,但我想从 map/flatMap 中获得正确的类型,这就是为什么我走上了 CanBuildFrom 的疯狂之路。
    猜你喜欢
    • 2013-05-13
    • 2015-12-07
    • 2015-12-16
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 1970-01-01
    • 1970-01-01
    • 2012-09-02
    相关资源
    最近更新 更多