Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Traverse.traverseM #1020

Merged
merged 1 commit into from
May 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/Traverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ import simulacrum.typeclass
def traverseU[A, GB](fa: F[A])(f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[F[U.A]] =
U.TC.traverse(fa)(a => U.subst(f(a)))(this)

/**
* A traverse followed by flattening the inner result.
*/
def traverseM[G[_], A, B](fa: F[A])(f: A => G[F[B]])(implicit G: Applicative[G], F: FlatMap[F]): G[F[B]] =
G.map(traverse(fa)(f))(F.flatten)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We got F (FlatMap[F]) as an Applicative[F], but we could also use the Traverse itself : G.map(traverse(fa)(f)(this))(F.flatten).

I am not sure if there is a reason to choose one over the other ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the version that you currently have is probably better, because Applicative.traverse ends up delegating through to Traverse.traverse anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called traverseA ? Consistent with replicateA. I think the M suffix is usually used when it has a Monad constraint.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adelbertc The Applicative[G] is required on traverse also. Here I think of the M as "monadic", referring to the FlatMap[F]. I guess we could call it traverseF instead, though that runs the risk of being confused with Functor.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not really sure about the M. traverseA seems a bit strange because we need FlatMap. traverseF could also be confusing with the flatMapF and mapF on (some) monad transformers.

Maybe we can call it traverseFlatten ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I totally did not see the FlatMap requirement.. the size of my screen stopped right at Applicative so I assumed that was all there was. I am good with traverseM in that case


/**
* Thread all the G effects through the F structure to invert the
* structure from F[G[A]] to G[F[A]].
Expand Down
87 changes: 84 additions & 3 deletions core/src/main/scala/cats/syntax/traverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,98 @@ trait TraverseSyntax extends TraverseSyntax1 {
}

final class TraverseOps[F[_], A](fa: F[A])(implicit F: Traverse[F]) {
def traverse[G[_]: Applicative, B](f: A => G[B]): G[F[B]] = F.traverse(fa)(f)
/**
* @see [[Traverse.traverse]]
*
* Example:
* {{{
* scala> import cats.data.Xor
* scala> import cats.std.list._
* scala> import cats.std.option._
* scala> import cats.syntax.traverse._
* scala> def parseInt(s: String): Option[Int] = Xor.catchOnly[NumberFormatException](s.toInt).toOption
* scala> List("1", "2", "3").traverse(parseInt)
* res0: Option[List[Int]] = Some(List(1, 2, 3))
* scala> List("1", "two", "3").traverse(parseInt)
* res1: Option[List[Int]] = None
* }}}
*/
def traverse[G[_]: Applicative, B](f: A => G[B]): G[F[B]] =
F.traverse(fa)(f)

/**
* @see [[Traverse.traverse]]
*
* Example:
* {{{
* scala> import cats.data.Xor
* scala> import cats.std.list._
* scala> import cats.syntax.traverse._
* scala> def parseInt(s: String): Xor[String, Int] = Xor.catchOnly[NumberFormatException](s.toInt).leftMap(_ => "no number")
* scala> val ns = List("1", "2", "3")
* scala> ns.traverseU(parseInt)
* res0: Xor[String, List[Int]] = Right(List(1, 2, 3))
* scala> ns.traverse[Xor[String, ?], Int](parseInt)
* res1: Xor[String, List[Int]] = Right(List(1, 2, 3))
* }}}
*/
def traverseU[GB](f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[F[U.A]] =
F.traverseU[A, GB](fa)(f)(U)

def sequence[G[_], B](implicit G: Applicative[G], ev: A =:= G[B]): G[F[B]] =
/**
* @see [[Traverse.traverseM]]
*
* Example:
* {{{
* scala> import cats.data.Xor
* scala> import cats.std.list._
* scala> import cats.std.option._
* scala> import cats.syntax.traverse._
* scala> def parseInt(s: String): Option[Int] = Xor.catchOnly[NumberFormatException](s.toInt).toOption
* scala> val x = Option(List("1", "two", "3"))
* scala> x.traverseM(_.map(parseInt))
* res0: List[Option[Int]] = List(Some(1), None, Some(3))
* }}}
*/
def traverseM[G[_]: Applicative, B](f: A => G[F[B]])(implicit F2: FlatMap[F]): G[F[B]] =
F.traverseM(fa)(f)

/**
* @see [[Traverse.sequence]]
*
* Example:
* {{{
* scala> import cats.std.list._
* scala> import cats.std.option._
* scala> import cats.syntax.traverse._
* scala> val x: List[Option[Int]] = List(Some(1), Some(2))
* scala> val y: List[Option[Int]] = List(None, Some(2))
* scala> x.sequence
* res0: Option[List[Int]] = Some(List(1, 2))
* scala> y.sequence
* res1: Option[List[Int]] = None
* }}}
*/
def sequence[G[_], B](implicit G: Applicative[G], ev: A =:= G[B]): G[F[B]] =
F.sequence(fa.asInstanceOf[F[G[B]]])

/**
* @see [[Traverse.sequenceU]]
*
* Example:
* {{{
* scala> import cats.data.{Validated, ValidatedNel}
* scala> import cats.std.list._
* scala> import cats.syntax.traverse._
* scala> val x: List[ValidatedNel[String, Int]] = List(Validated.valid(1), Validated.invalid("a"), Validated.invalid("b")).map(_.toValidatedNel)
* scala> x.sequenceU
* res0: cats.data.ValidatedNel[String,List[Int]] = Invalid(OneAnd(a,List(b)))
* scala> x.sequence[ValidatedNel[String, ?], Int]
* res1: cats.data.ValidatedNel[String,List[Int]] = Invalid(OneAnd(a,List(b)))
* }}}
*/
def sequenceU(implicit U: Unapply[Applicative,A]): U.M[F[U.A]] =
F.sequenceU[A](fa)(U)

}

final class NestedTraverseOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Traverse[F]) {
Expand Down
9 changes: 8 additions & 1 deletion tests/src/test/scala/cats/tests/SyntaxTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,14 @@ class SyntaxTests extends AllInstances with AllSyntax {
val as2: List[A] = fa.dropWhile_(f5)
}

def testTraverse[F[_]: Traverse, G[_]: Applicative, A]: Unit = {
def testTraverse[F[_]: Traverse: FlatMap, G[_]: Applicative, A, B]: Unit = {
val fa = mock[F[A]]
val f1 = mock[A => G[B]]
val gfb: G[F[B]] = fa.traverse(f1)

val f2 = mock[A => G[F[B]]]
val gfb2: G[F[B]] = fa.traverseM(f2)

val fga = mock[F[G[A]]]
val gunit: G[F[A]] = fga.sequence
}
Expand Down