Skip to content

Commit

Permalink
Merge pull request #3152 from travisbrown/topic/type-class-methods
Browse files Browse the repository at this point in the history
"Move" type class syntax methods onto type classes
  • Loading branch information
travisbrown authored Nov 15, 2019
2 parents 4563806 + 7b30d5b commit 291af38
Show file tree
Hide file tree
Showing 17 changed files with 461 additions and 188 deletions.
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 @@ -245,6 +246,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

0 comments on commit 291af38

Please sign in to comment.