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

WIP: add some missing applicative and monad ops #1216

Closed
wants to merge 3 commits into from
Closed
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
8 changes: 8 additions & 0 deletions core/src/main/scala/cats/Applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ import simulacrum.typeclass
def replicateA[A](n: Int, fa: F[A]): F[List[A]] =
sequence(List.fill(n)(fa))

/** Returns the given argument if `cond` is `false`, otherwise, unit lifted into F. */
def unlessA[A](cond: Boolean)(f: => F[A]): F[Unit] =
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the story on call-by-name vs Eval[F[A]]. Seems like the later is more the cats style (maybe have both, strict and Eval)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok I'll change those to strict + Eval. Honestly I have never had a case where it mattered, but scalaz does it this way so it's probably worthwhile to have the nonstrict version.

if (cond) pure(()) else void(f)

/** Returns the given argument if `cond` is `true`, otherwise, unit lifted into F. */
def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] =
if (cond) void(f) else pure(())

def traverse[A, G[_], B](value: G[A])(f: A => F[B])(implicit G: Traverse[G]): F[G[B]] =
G.traverse(value)(f)(this)

Expand Down
55 changes: 55 additions & 0 deletions core/src/main/scala/cats/Monad.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,61 @@ import simulacrum.typeclass
* Must obey the laws defined in cats.laws.MonadLaws.
*/
@typeclass trait Monad[F[_]] extends FlatMap[F] with Applicative[F] {

override def map[A, B](fa: F[A])(f: A => B): F[B] =
flatMap(fa)(a => pure(f(a)))

/**
* Execute an action repeatedly as long as the given `Boolean` expression
* returns `true`. The condition is evalated before the loop body.
* Collects the results into an arbitrary `MonadCombine` value, such as a `List`.
*/
def whileM[G[_], A](p: F[Boolean])(body: => F[A])(implicit G: MonadCombine[G]): F[G[A]] = {
lazy val f = body
ifM(p)(flatMap(f)(x => map(whileM(p)(f))(xs => G.combineK(G.pure(x), xs))), pure(G.empty))
}

/**
* Execute an action repeatedly as long as the given `Boolean` expression
* returns `true`. The condition is evaluated before the loop body.
* Discards results.
*/
def whileM_[A](p: F[Boolean])(body: => F[A]): F[Unit] = {
lazy val f = body
ifM(p)(flatMap(f)(_ => whileM_(p)(f)), pure(()))
}

/**
* Execute an action repeatedly until the `Boolean` condition returns `true`.
* The condition is evaluated after the loop body. Collects results into an
* arbitrary `MonadCombine` value, such as a `List`.
*/
def untilM[G[_], A](f: F[A])(cond: => F[Boolean])(implicit G: MonadCombine[G]): F[G[A]] = {
lazy val p = cond
flatMap(f)(x => map(whileM(map(p)(!_))(f))(xs => G.combineK(G.pure(x), xs)))
}

/**
* Execute an action repeatedly until the `Boolean` condition returns `true`.
* The condition is evaluated after the loop body. Discards results.
*/
def untilM_[A](f: F[A])(cond: => F[Boolean]): F[Unit] = {
lazy val p = cond
flatMap(f)(_ => whileM_(map(p)(!_))(f))
}

/**
* Execute an action repeatedly until its result fails to satisfy the given predicate
* and return that result, discarding all others.
*/
def iterateWhile[A](f: F[A])(p: A => Boolean): F[A] =
flatMap(f)(y => if (p(y)) iterateWhile(f)(p) else pure(y))

/**
* Execute an action repeatedly until its result satisfies the given predicate
* and return that result, discarding all others.
*/
def iterateUntil[A](f: F[A])(p: A => Boolean): F[A] =
flatMap(f)(y => if (p(y)) pure(y) else iterateUntil(f)(p))

}
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ trait AllSyntax
with GroupSyntax
with InvariantSyntax
with ListSyntax
with MonadSyntax
with MonadCombineSyntax
with MonadErrorSyntax
with MonadFilterSyntax
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/scala/cats/syntax/applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ package syntax
trait ApplicativeSyntax {
implicit def catsSyntaxApplicativeId[A](a: A): ApplicativeIdOps[A] = new ApplicativeIdOps[A](a)
implicit def catsSyntaxApplicativeEval[A](a: Eval[A]): ApplicativeEvalOps[A] = new ApplicativeEvalOps[A](a)
implicit def catsSyntaxApplicativeReplicateA[F[_]: Applicative, A](fa: F[A]): ReplicateAOps[F, A] = new ReplicateAOps(fa)
implicit def catsSyntaxApplicativeUnlessA[F[_]: Applicative, A](fa: F[A]): UnlessAOps[F, A] = new UnlessAOps(fa)
implicit def catsSyntaxApplicativeWhenA[F[_]: Applicative, A](fa: F[A]): WhenAOps[F, A] = new WhenAOps(fa)
}

final class ApplicativeIdOps[A](val a: A) extends AnyVal {
Expand All @@ -13,3 +16,15 @@ final class ApplicativeIdOps[A](val a: A) extends AnyVal {
final class ApplicativeEvalOps[A](val a: Eval[A]) extends AnyVal {
def pureEval[F[_]](implicit F: Applicative[F]): F[A] = F.pureEval(a)
}

final class ReplicateAOps[F[_], A](fa: F[A])(implicit F: Applicative[F]) {
def replicateA(n: Int): F[List[A]] = F.replicateA(n, fa)
}

final class UnlessAOps[F[_], A](fa: => F[A])(implicit F: Applicative[F]) {
def unlessA(cond: Boolean): F[Unit] = F.unlessA(cond)(fa)
}

final class WhenAOps[F[_], A](fa: => F[A])(implicit F: Applicative[F]) {
def whenA(cond: Boolean): F[Unit] = F.whenA(cond)(fa)
}
27 changes: 27 additions & 0 deletions core/src/main/scala/cats/syntax/monad.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cats
package syntax

trait MonadSyntax {
implicit def catsSyntaxMonadWhileM[F[_]: Monad, A](fa: F[A]): WhileMOps[F, A] = new WhileMOps(fa)
implicit def catsSyntaxMonadUntilM[F[_]: Monad, A](fa: F[A]): UntilMOps[F, A] = new UntilMOps(fa)
implicit def catsSyntaxMonadIterateWhile[F[_]: Monad, A](fa: F[A]): IterateWhileOps[F, A] = new IterateWhileOps(fa)
implicit def catsSyntaxMonadIterateUntil[F[_]: Monad, A](fa: F[A]): IterateUntilOps[F, A] = new IterateUntilOps(fa)
}

final class WhileMOps[F[_], A](fa: F[A])(implicit M: Monad[F]) {
def whileM[G[_]](p: F[Boolean])(implicit G: MonadCombine[G]): F[G[A]] = M.whileM(p)(fa)
def whileM_[G[_]](p: F[Boolean])(implicit G: MonadCombine[G]): F[Unit] = M.whileM_(p)(fa)
}

final class UntilMOps[F[_], A](fa: F[A])(implicit M: Monad[F]) {
def untilM[G[_]](p: F[Boolean])(implicit G: MonadCombine[G]): F[G[A]] = M.untilM(fa)(p)
def untilM_[G[_]](p: F[Boolean])(implicit G: MonadCombine[G]): F[Unit] = M.untilM_(fa)(p)
}

final class IterateWhileOps[F[_], A](fa: F[A])(implicit M: Monad[F]) {
def iterateWhile(p: A => Boolean): F[A] = M.iterateWhile(fa)(p)
}

final class IterateUntilOps[F[_], A](fa: F[A])(implicit M: Monad[F]) {
def iterateUntil(p: A => Boolean): F[A] = M.iterateUntil(fa)(p)
}