Victor Basso
Victor Basso

Reputation: 5796

Scala cats, traverse Seq

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]?

Upvotes: 11

Views: 4465

Answers (3)

Cory Klein
Cory Klein

Reputation: 55620

As of cats 2.3, support for immutable.Seq is now built in. See "Where are implicit instances for Seq?" on the FAQ or this PR where the functionality was added.

Upvotes: 1

LMeyer
LMeyer

Reputation: 2631

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.

EDIT : Nevermind, it exists now, see linked thread

Upvotes: 6

Andrey Tyukin
Andrey Tyukin

Reputation: 44908

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
  }
}    

Upvotes: 0

Related Questions