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

Added more implementations of map2Eval #1819

Merged
merged 3 commits into from
Aug 17, 2017
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
1 change: 1 addition & 0 deletions core/src/main/scala/cats/data/EitherT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ private[data] trait EitherTFunctor[F[_], L] extends Functor[EitherT[F, L, ?]] {
private[data] trait EitherTMonad[F[_], L] extends Monad[EitherT[F, L, ?]] with EitherTFunctor[F, L] {
implicit val F: Monad[F]
def pure[A](a: A): EitherT[F, L, A] = EitherT.pure(a)

def flatMap[A, B](fa: EitherT[F, L, A])(f: A => EitherT[F, L, B]): EitherT[F, L, B] = fa flatMap f
def tailRecM[A, B](a: A)(f: A => EitherT[F, L, Either[A, B]]): EitherT[F, L, B] =
EitherT(F.tailRecM(a)(a0 => F.map(f(a0).value) {
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/data/IdT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ private[data] sealed trait IdTApply[F[_]] extends Apply[IdT[F, ?]] with IdTFunct
implicit val F0: Apply[F]

override def ap[A, B](ff: IdT[F, A => B])(fa: IdT[F, A]): IdT[F, B] = fa.ap(ff)

override def map2Eval[A, B, Z](fa: IdT[F, A], fb: Eval[IdT[F, B]])(f: (A, B) => Z): Eval[IdT[F, Z]] =
F0.map2Eval(fa.value, fb.map(_.value))(f) // if F0 has a lazy map2Eval, leverage it
.map(IdT(_))
}

private[data] sealed trait IdTApplicative[F[_]] extends Applicative[IdT[F, ?]] with IdTApply[F] {
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/Ior.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ private[data] sealed abstract class IorInstances extends IorInstances0 {

def flatMap[B, C](fa: Ior[A, B])(f: B => Ior[A, C]): Ior[A, C] = fa.flatMap(f)

override def map2Eval[B, C, Z](fa: Ior[A, B], fb: Eval[Ior[A, C]])(f: (B, C) => Z): Eval[Ior[A, Z]] =
fa match {
case l @ Ior.Left(_) => Eval.now(l) // no need to evaluate fb
case notLeft => fb.map(fb => map2(notLeft, fb)(f))
}

def tailRecM[B, C](b: B)(fn: B => Ior[A, Either[B, C]]): A Ior C = {
@tailrec
def loop(v: Ior[A, Either[B, C]]): A Ior C = v match {
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/data/Tuple2K.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ private[data] sealed trait Tuple2KApply[F[_], G[_]] extends Apply[λ[α => Tuple
Tuple2K(F.ap(f.first)(fa.first), G.ap(f.second)(fa.second))
override def product[A, B](fa: Tuple2K[F, G, A], fb: Tuple2K[F, G, B]): Tuple2K[F, G, (A, B)] =
Tuple2K(F.product(fa.first, fb.first), G.product(fa.second, fb.second))
override def map2Eval[A, B, Z](fa: Tuple2K[F, G, A], fb: Eval[Tuple2K[F, G, B]])(f: (A, B) => Z): Eval[Tuple2K[F, G, Z]] = {
val fbmemo = fb.memoize // don't recompute this twice internally
for {
fz <- F.map2Eval(fa.first, fbmemo.map(_.first))(f)
gz <- G.map2Eval(fa.second, fbmemo.map(_.second))(f)
} yield Tuple2K(fz, gz)
}
}

private[data] sealed trait Tuple2KApplicative[F[_], G[_]] extends Applicative[λ[α => Tuple2K[F, G, α]]] with Tuple2KApply[F, G] {
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/WriterT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ private[data] sealed trait WriterTApply[F[_], L] extends WriterTFunctor[F, L] wi

def ap[A, B](f: WriterT[F, L, A => B])(fa: WriterT[F, L, A]): WriterT[F, L, B] =
fa ap f

override def map2Eval[A, B, Z](fa: WriterT[F, L, A], fb: Eval[WriterT[F, L, B]])(f: (A, B) => Z): Eval[WriterT[F, L, Z]] =
F0.map2Eval(fa.run, fb.map(_.run)) { case ((la, a), (lb, b)) => (L0.combine(la, lb), f(a, b)) }
.map(WriterT(_)) // F0 may have a lazy map2Eval

override def product[A, B](fa: WriterT[F, L, A], fb: WriterT[F, L, B]): WriterT[F, L, (A, B)] =
WriterT(F0.map(F0.product(fa.run, fb.run)) { case ((l1, a), (l2, b)) => (L0.combine(l1, l2), (a, b)) })
}
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/scala/cats/instances/list.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
fa.flatMap(f)

override def map2[A, B, Z](fa: List[A], fb: List[B])(f: (A, B) => Z): List[Z] =
fa.flatMap(a => fb.map(b => f(a, b)))
if (fb.isEmpty) Nil // do O(1) work if fb is empty
else fa.flatMap(a => fb.map(b => f(a, b))) // already O(1) if fa is empty

override def map2Eval[A, B, Z](fa: List[A], fb: Eval[List[B]])(f: (A, B) => Z): Eval[List[Z]] =
if (fa.isEmpty) Eval.now(Nil) // no need to evaluate fb
else fb.map(fb => map2(fa, fb)(f))

def tailRecM[A, B](a: A)(f: A => List[Either[A, B]]): List[B] = {
val buf = List.newBuilder[B]
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/scala/cats/instances/map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
fa.map { case (k, a) => (k, f(a)) }

override def map2[A, B, Z](fa: Map[K, A], fb: Map[K, B])(f: (A, B) => Z): Map[K, Z] =
fa.flatMap { case (k, a) => fb.get(k).map(b => (k, f(a, b))) }
if (fb.isEmpty) Map.empty // do O(1) work if fb is empty
else fa.flatMap { case (k, a) => fb.get(k).map(b => (k, f(a, b))) }

override def map2Eval[A, B, Z](fa: Map[K, A], fb: Eval[Map[K, B]])(f: (A, B) => Z): Eval[Map[K, Z]] =
if (fa.isEmpty) Eval.now(Map.empty) // no need to evaluate fb
else fb.map(fb => map2(fa, fb)(f))

override def ap[A, B](ff: Map[K, A => B])(fa: Map[K, A]): Map[K, B] =
fa.flatMap { case (k, a) => ff.get(k).map(f => (k, f(a))) }
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/scala/cats/instances/stream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances {
def flatMap[A, B](fa: Stream[A])(f: A => Stream[B]): Stream[B] =
fa.flatMap(f)

override def map2[A, B, Z](fa: Stream[A], fb: Stream[B])(f: (A, B) => Z): Stream[Z] =
if (fb.isEmpty) Stream.empty // do O(1) work if fb is empty
else fa.flatMap(a => fb.map(b => f(a, b))) // already O(1) if fa is empty

override def map2Eval[A, B, Z](fa: Stream[A], fb: Eval[Stream[B]])(f: (A, B) => Z): Eval[Stream[Z]] =
if (fa.isEmpty) Eval.now(Stream.empty) // no need to evaluate fb
else fb.map(fb => map2(fa, fb)(f))

def coflatMap[A, B](fa: Stream[A])(f: Stream[A] => B): Stream[B] =
fa.tails.toStream.init.map(f)

Expand Down
6 changes: 6 additions & 0 deletions laws/src/main/scala/cats/laws/ApplyLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ trait ApplyLaws[F[_]] extends FunctorLaws[F] with CartesianLaws[F] {
val compose: (B => C) => (A => B) => (A => C) = _.compose
fbc.ap(fab.ap(fa)) <-> fbc.map(compose).ap(fab).ap(fa)
}

def map2ProductConsistency[A, B, C](fa: F[A], fb: F[B], f: (A, B) => C): IsEq[F[C]] =
F.map(F.product(fa, fb)) { case (a, b) => f(a, b) } <-> F.map2(fa, fb)(f)

def map2EvalConsistency[A, B, C](fa: F[A], fb: F[B], f: (A, B) => C): IsEq[F[C]] =
F.map2(fa, fb)(f) <-> (F.map2Eval(fa, Eval.now(fb))(f).value)
}

object ApplyLaws {
Expand Down
5 changes: 4 additions & 1 deletion laws/src/main/scala/cats/laws/discipline/ApplyTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with CartesianTests[F] {
val name = "apply"
val parents = Seq(functor[A, B, C], cartesian[A, B, C])
val bases = Seq.empty
val props = Seq("apply composition" -> forAll(laws.applyComposition[A, B, C] _))
val props = Seq(
"apply composition" -> forAll(laws.applyComposition[A, B, C] _),
"map2/product-map consistency" -> forAll(laws.map2ProductConsistency[A, B, C] _),
"map2/map2Eval consistency" -> forAll(laws.map2EvalConsistency[A, B, C] _))
}
}

Expand Down