How to understand traverse, traverseU and traverseM
sequence
is used to gather together applicative effects. More concretely, it lets you "flip" F[G[A]]
to G[F[A]]
, provided G
is Applicative
and F
is Traversable
. So we can use it to "pull together" a bunch of Applicative
effects (note all Monad
s are Applicative
):
List(Future.successful(1), Future.successful(2)).sequence : Future[List[Int]]
// = Future.successful(List(1, 2))
List(4.set("abc"), 5.set("def")).sequence : Writer[String, List[Int]]
// = List(4, 5).set("abcdef")
traverse
is equivalent to map
then sequence
, so you can use it when you have a function that returns an Applicative
and you want to just get a single instance of your Applicative
rather than a list of them:
def fetchPost(postId: Int): Future[String]
//Fetch each post, but we only want an overall `Future`, not a `List[Future]`
List(1, 2).traverse[Future, String](fetchPost): Future[List[String]]
traverseU
is the same operation as traverse
, just with the types expressed differently so that the compiler can infer them more easily.
def logConversion(s: String): Writer[Vector[String], Int] =
s.toInt.set(Vector(s"Converted $s"))
List("4", "5").traverseU(logConversion): Writer[Vector[String], List[Int]]
// = List("4", "5").map(logConversion).sequence
// = List(4.set("Converted 4"), 5.set("Converted 5")).sequence
// = List(4, 5).set(Vector("Converted 4", "Converted 5"))
traverseM(f)
is equivalent to traverse(f).map(_.join)
, where join
is the scalaz name for flatten
. It's useful as a kind of "lifting flatMap":
def multiples(i: Int): Future[List[Int]] =
Future.successful(List(i, i * 2, i * 3))
List(1, 10).map(multiples): List[Future[List[Int]]] //hard to work with
List(1, 10).traverseM(multiples): Future[List[Int]]
// = List(1, 10).traverse(multiples).map(_.flatten)
// = List(1, 10).map(multiples).sequence.map(_.flatten)
// = List(Future.successful(List(1, 2, 3)), Future.successful(List(10, 20, 30)))
// .sequence.map(_.flatten)
// = Future.successful(List(List(1, 2, 3), List(10, 20, 30))).map(_.flatten)
// = Future.successful(List(1, 2, 3, 10, 20, 30))
Xiaohe Dong
Updated on June 15, 2022Comments
-
Xiaohe Dong about 2 years
I am confused about the usage case about traverse, traverseU and traverseM, I searched it in the scalaz website, the simple code example:
def sum(x: Int) = x + 1 List(1,2,3).traverseU(sum)
it looks like it is similar to (map and aggregate):
List(1,2,3).map(sum).reduceLeft(_ + _)
I think it is more than that for traverseU, I just wonder what is the difference between those 3 method, it would be better I will have some sample code to show the difference
Many thanks in advance
-
Xiaohe Dong over 9 yearsthanks, I upvote, so I just wonder you mentioned that the difference between traverse and traverseU is the type inference, is this only difference, because as far as I am concerned, I will only use traverseU instead of traverse
-
lmm over 9 yearsYes, that's the only difference. (Technically it uses an extra parameter to perform the inference, but I'd expect the JVM to optimize this away). In the case where
traverseU
doesn't infer the type parameters and you need to specify them by hand (or if you're writing generic code where the type you're traversing is itself a type parameter), it's easier to do withtraverse
(which doesn't have the inference helper), but I think that's the only case where you'd ever want to usetraverse
rather thantraverseU
-
Xiaohe Dong over 9 yearsBTW, I think the code should be Future.now instead of Future.successful, because the previous one is from scalaz, so it will have implicit value, but the later one from standard library does not have implicit resolution
-
lmm over 9 yearsI believe there are instances for the standard library
Future
if you have the appropriate imports in scope (from somewhere inscalaz.std
).