11

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

Victor Basso
  • 5,556
  • 5
  • 42
  • 60
  • May I ask why exactly you want to keep wrapping and unwrapping `Seq` to `List` and back, instead of converting everything to `List` once and then work with that? – Andrey Tyukin Feb 16 '18 at 18:28
  • In the project I'm working on `Seq` is used everywhere, not sure why. Perhaps we could've been using `List` instead. – Victor Basso Feb 16 '18 at 20:28

3 Answers3

6

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

LMeyer
  • 2,570
  • 1
  • 24
  • 35
1

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.

Cory Klein
  • 51,188
  • 43
  • 183
  • 243
0

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
  }
}    
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93