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

"Move" type class syntax methods onto type classes #3152

Merged
merged 16 commits into from
Nov 15, 2019
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
45 changes: 44 additions & 1 deletion core/src/main/scala/cats/ApplicativeError.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cats

import cats.data.EitherT
import cats.data.{EitherT, Validated}
import cats.data.Validated.{Invalid, Valid}

import scala.reflect.ClassTag
import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -186,6 +187,48 @@ trait ApplicativeError[F[_], E] extends Applicative[F] {
case Left(e) => raiseError(e)
}

/**
* Convert from scala.Option
*
* Example:
* {{{
* scala> import cats.implicits._
* scala> import cats.ApplicativeError
* scala> val F = ApplicativeError[Either[String, *], String]
*
* scala> F.fromOption(Some(1), "Empty")
* res0: scala.Either[String, Int] = Right(1)
*
* scala> F.fromOption(Option.empty[Int], "Empty")
* res1: scala.Either[String, Int] = Left(Empty)
* }}}
*/
def fromOption[A](oa: Option[A], ifEmpty: => E): F[A] =
oa match {
case Some(a) => pure(a)
case None => raiseError(ifEmpty)
}

/**
* Convert from cats.data.Validated
*
* Example:
* {{{
* scala> import cats.implicits._
* scala> import cats.ApplicativeError
*
* scala> ApplicativeError[Option, Unit].fromValidated(1.valid[Unit])
* res0: scala.Option[Int] = Some(1)
*
* scala> ApplicativeError[Option, Unit].fromValidated(().invalid[Int])
* res1: scala.Option[Int] = None
* }}}
*/
def fromValidated[A](x: Validated[E, A]): F[A] =
x match {
case Invalid(e) => raiseError(e)
case Valid(a) => pure(a)
}
}

object ApplicativeError {
Expand Down
36 changes: 34 additions & 2 deletions core/src/main/scala/cats/Apply.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cats

import simulacrum.typeclass
import simulacrum.noop
import simulacrum.{noop, typeclass}
import cats.data.Ior

/**
Expand Down Expand Up @@ -214,6 +213,39 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit
val G = Apply[G]
}

/**
* An `if-then-else` lifted into the `F` context.
* This function combines the effects of the `fcond` condition and of the two branches,
* in the order in which they are given.
*
* The value of the result is, depending on the value of the condition,
* the value of the first argument, or the value of the second argument.
*
* Example:
* {{{
* scala> import cats.implicits._
*
* scala> val b1: Option[Boolean] = Some(true)
* scala> val asInt1: Option[Int] = Apply[Option].ifA(b1)(Some(1), Some(0))
* scala> asInt1.get
* res0: Int = 1
*
* scala> val b2: Option[Boolean] = Some(false)
* scala> val asInt2: Option[Int] = Apply[Option].ifA(b2)(Some(1), Some(0))
* scala> asInt2.get
* res1: Int = 0
*
* scala> val b3: Option[Boolean] = Some(true)
* scala> val asInt3: Option[Int] = Apply[Option].ifA(b3)(Some(1), None)
* asInt2: Option[Int] = None
*
* }}}
*/
@noop
def ifA[A](fcond: F[Boolean])(ifTrue: F[A], ifFalse: F[A]): F[A] = {
def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse
ap2(map(fcond)(ite))(ifTrue, ifFalse)
}
}

object Apply {
Expand Down
48 changes: 47 additions & 1 deletion core/src/main/scala/cats/Bitraverse.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats

import simulacrum.typeclass
import simulacrum.{noop, typeclass}

/**
* A type class abstracting over types that give rise to two independent [[cats.Traverse]]s.
Expand Down Expand Up @@ -61,6 +61,52 @@ import simulacrum.typeclass

override def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D] =
bitraverse[Id, A, B, C, D](fab)(f, g)

/**
* Traverse over the left side of the structure.
* For the right side, use the standard `traverse` from [[cats.Traverse]].
*
* Example:
* {{{
* scala> import cats.implicits._
*
* scala> val intAndString: (Int, String) = (7, "test")
*
* scala> Bitraverse[Tuple2].leftTraverse(intAndString)(i => Option(i).filter(_ > 5))
* res1: Option[(Int, String)] = Some((7,test))
*
* scala> Bitraverse[Tuple2].leftTraverse(intAndString)(i => Option(i).filter(_ < 5))
* res2: Option[(Int, String)] = None
* }}}
*/
@noop
def leftTraverse[G[_], A, B, C](fab: F[A, B])(f: A => G[C])(implicit G: Applicative[G]): G[F[C, B]] =
bitraverse(fab)(f, G.pure(_))

/**
* Sequence the left side of the structure.
* For the right side, use the standard `sequence` from [[cats.Traverse]].
*
* Example:
* {{{
* scala> import cats.implicits._
*
* scala> val optionalErrorRight: Either[Option[String], Int] = Either.right(123)
* scala> optionalErrorRight.leftSequence
* res1: Option[Either[String, Int]] = Some(Right(123))
*
* scala> val optionalErrorLeftSome: Either[Option[String], Int] = Either.left(Some("something went wrong"))
* scala> optionalErrorLeftSome.leftSequence
* res2: Option[Either[String, Int]] = Some(Left(something went wrong))
*
* scala> val optionalErrorLeftNone: Either[Option[String], Int] = Either.left(None)
* scala> optionalErrorLeftNone.leftSequence
* res3: Option[Either[String,Int]] = None
* }}}
*/
@noop
def leftSequence[G[_], A, B](fgab: F[G[A], B])(implicit G: Applicative[G]): G[F[A, B]] =
bitraverse(fgab)(identity, G.pure(_))
}

private[cats] trait ComposedBitraverse[F[_, _], G[_, _]]
Expand Down
46 changes: 46 additions & 0 deletions core/src/main/scala/cats/FlatMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,50 @@ import simulacrum.noop
*/
def flatTap[A, B](fa: F[A])(f: A => F[B]): F[A] =
flatMap(fa)(a => as(f(a), a))

/**
* Like an infinite loop of >> calls. This is most useful effect loops
* that you want to run forever in for instance a server.
*
* This will be an infinite loop, or it will return an F[Nothing].
*
* Be careful using this.
* For instance, a List of length k will produce a list of length k^n at iteration
* n. This means if k = 0, we return an empty list, if k = 1, we loop forever
* allocating single element lists, but if we have a k > 1, we will allocate
* exponentially increasing memory and very quickly OOM.
*/
@noop
def foreverM[A, B](fa: F[A]): F[B] = {
// allocate two things once for efficiency.
val leftUnit = Left(())
val stepResult: F[Either[Unit, B]] = map(fa)(_ => leftUnit)
tailRecM(())(_ => stepResult)
}

/**
* iterateForeverM is almost exclusively useful for effect types. For instance,
* A may be some state, we may take the current state, run some effect to get
* a new state and repeat.
*/
@noop
def iterateForeverM[A, B](a: A)(f: A => F[A]): F[B] =
tailRecM[A, B](a)(f.andThen { fa =>
map(fa)(Left(_): Either[A, B])
})

/**
* This repeats an F until we get defined values. This can be useful
* for polling type operations on State (or RNG) Monads, or in effect
* monads.
*/
@noop
def untilDefinedM[A](foa: F[Option[A]]): F[A] = {
val leftUnit: Either[Unit, A] = Left(())
val feither: F[Either[Unit, A]] = map(foa) {
case None => leftUnit
case Some(a) => Right(a)
}
tailRecM(())(_ => feither)
}
}
Loading