Scala cats, traverse Seq

我与影子孤独终老i 提交于 2019-12-08 19:10:40

问题


I know I can traverse Lists

import cats.instances.list._
import cats.syntax.traverse._

def doMagic(item: A): M[B] = ???
val list: List[A] = ???
val result: M[List[B]] = list.traverse(doMagic)

And I can convert a Seq back and forth to List

val seq: Seq[A] = ???
val result: M[Seq[B]] = seq.toList.traverse(doMagic).map(_.toSeq)

But can I also traverse Seq without the boilerplate?

val seq: Seq[A] = ???
val result: M[Seq[B]] = seq.traverse(doMagic)

Or what's an easy way to get an instance of Traverse[Seq]?


回答1:


Cats does not provide typeclass instances for Seq, so besides implementing it yourself you're stuck with the conversion.

As to why, there's an ongoing discussion in an (somewhat old) Cats issue. To sum it up, you won't know enough about Seq underlying characteristics to make sure some of the typeclasses instances laws hold.




回答2:


If you are absolutely sure that the conversion from all Seq to List will always succeed in your code, you can simply transfer the Traverse structure from List to Seq over an (pseudo-)isomorphism:

  def traverseFromIso[F[_], Z[_]]
    (forward: F ~> Z, inverse: Z ~> F)
    (implicit zt: Traverse[Z])
  : Traverse[F] = new Traverse[F] {
    def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) ⇒ B): B = zt.foldLeft(forward(fa), b)(f)
    def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
      zt.foldRight(forward(fa), lb)(f)

    def traverse[G[_], A, B]
      (fa: F[A])
      (f: (A) ⇒ G[B])
      (implicit appG: Applicative[G])
    : G[F[B]] = {
      (zt.traverse(forward(fa))(f)(appG)).map(zb => inverse(zb))
    }
  }

This isn't really an isomorphism, because the conversion from Seq to List can fail badly (e.g. if the sequence is infinite). What it does is simply converting Seq to List back and forth, and forwarding all method calls to those of Traverse[List].

Now you can use this method to build an instance of Traverse[Seq]:

 implicit val seqTraverse: Traverse[Seq] = traverseFromIso(
   new FunctionK[Seq, List] { def apply[X](sx: Seq[X]): List[X] = sx.toList },
   new FunctionK[List, Seq] { def apply[X](lx: List[X]): Seq[X] = lx }
 )

Full code snippet (compiles with scala 2.12.4 and cats 1.0.1):

import cats._
import cats.implicits._
import cats.arrow.FunctionK
import scala.language.higherKinds

object TraverseFromIso {

  // This method can build you a `Traversable[Seq]` from
  // an `Traversable[List]` and a pair of polymorphic conversion
  // functions:

  def traverseFromIso[F[_], Z[_]]
    (forward: F ~> Z, inverse: Z ~> F)
    (implicit zt: Traverse[Z])
  : Traverse[F] = new Traverse[F] {
    def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) ⇒ B): B = zt.foldLeft(forward(fa), b)(f)
    def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
      zt.foldRight(forward(fa), lb)(f)

    def traverse[G[_], A, B]
      (fa: F[A])
      (f: (A) ⇒ G[B])
      (implicit appG: Applicative[G])
    : G[F[B]] = {
      (zt.traverse(forward(fa))(f)(appG)).map(zb => inverse(zb))
    }
  }

  // A little demo
  def main(args: Array[String]): Unit = {

    // To instantiate a `Traverse[Seq]`, we have to provide
    // two natural transformations (from List to Seq and back):

    implicit val seqTraverse: Traverse[Seq] = traverseFromIso(
      new FunctionK[Seq, List] { def apply[X](sx: Seq[X]): List[X] = sx.toList },
      new FunctionK[List, Seq] { def apply[X](lx: List[X]): Seq[X] = lx }
    )

    // do stuff with `Traversable[Seq]` here
  }
}    


来源:https://stackoverflow.com/questions/48828399/scala-cats-traverse-seq

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!