Skip to content

Commit 291af38

Browse files
authored
Merge pull request #3152 from travisbrown/topic/type-class-methods
"Move" type class syntax methods onto type classes
2 parents 4563806 + 7b30d5b commit 291af38

17 files changed

Lines changed: 461 additions & 188 deletions

core/src/main/scala/cats/ApplicativeError.scala

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cats
22

3-
import cats.data.EitherT
3+
import cats.data.{EitherT, Validated}
4+
import cats.data.Validated.{Invalid, Valid}
45

56
import scala.reflect.ClassTag
67
import scala.util.{Failure, Success, Try}
@@ -245,6 +246,48 @@ trait ApplicativeError[F[_], E] extends Applicative[F] {
245246
case Left(e) => raiseError(e)
246247
}
247248

249+
/**
250+
* Convert from scala.Option
251+
*
252+
* Example:
253+
* {{{
254+
* scala> import cats.implicits._
255+
* scala> import cats.ApplicativeError
256+
* scala> val F = ApplicativeError[Either[String, *], String]
257+
*
258+
* scala> F.fromOption(Some(1), "Empty")
259+
* res0: scala.Either[String, Int] = Right(1)
260+
*
261+
* scala> F.fromOption(Option.empty[Int], "Empty")
262+
* res1: scala.Either[String, Int] = Left(Empty)
263+
* }}}
264+
*/
265+
def fromOption[A](oa: Option[A], ifEmpty: => E): F[A] =
266+
oa match {
267+
case Some(a) => pure(a)
268+
case None => raiseError(ifEmpty)
269+
}
270+
271+
/**
272+
* Convert from cats.data.Validated
273+
*
274+
* Example:
275+
* {{{
276+
* scala> import cats.implicits._
277+
* scala> import cats.ApplicativeError
278+
*
279+
* scala> ApplicativeError[Option, Unit].fromValidated(1.valid[Unit])
280+
* res0: scala.Option[Int] = Some(1)
281+
*
282+
* scala> ApplicativeError[Option, Unit].fromValidated(().invalid[Int])
283+
* res1: scala.Option[Int] = None
284+
* }}}
285+
*/
286+
def fromValidated[A](x: Validated[E, A]): F[A] =
287+
x match {
288+
case Invalid(e) => raiseError(e)
289+
case Valid(a) => pure(a)
290+
}
248291
}
249292

250293
object ApplicativeError {

core/src/main/scala/cats/Apply.scala

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cats
22

3-
import simulacrum.typeclass
4-
import simulacrum.noop
3+
import simulacrum.{noop, typeclass}
54
import cats.data.Ior
65

76
/**
@@ -214,6 +213,39 @@ trait Apply[F[_]] extends Functor[F] with InvariantSemigroupal[F] with ApplyArit
214213
val G = Apply[G]
215214
}
216215

216+
/**
217+
* An `if-then-else` lifted into the `F` context.
218+
* This function combines the effects of the `fcond` condition and of the two branches,
219+
* in the order in which they are given.
220+
*
221+
* The value of the result is, depending on the value of the condition,
222+
* the value of the first argument, or the value of the second argument.
223+
*
224+
* Example:
225+
* {{{
226+
* scala> import cats.implicits._
227+
*
228+
* scala> val b1: Option[Boolean] = Some(true)
229+
* scala> val asInt1: Option[Int] = Apply[Option].ifA(b1)(Some(1), Some(0))
230+
* scala> asInt1.get
231+
* res0: Int = 1
232+
*
233+
* scala> val b2: Option[Boolean] = Some(false)
234+
* scala> val asInt2: Option[Int] = Apply[Option].ifA(b2)(Some(1), Some(0))
235+
* scala> asInt2.get
236+
* res1: Int = 0
237+
*
238+
* scala> val b3: Option[Boolean] = Some(true)
239+
* scala> val asInt3: Option[Int] = Apply[Option].ifA(b3)(Some(1), None)
240+
* asInt2: Option[Int] = None
241+
*
242+
* }}}
243+
*/
244+
@noop
245+
def ifA[A](fcond: F[Boolean])(ifTrue: F[A], ifFalse: F[A]): F[A] = {
246+
def ite(b: Boolean)(ifTrue: A, ifFalse: A) = if (b) ifTrue else ifFalse
247+
ap2(map(fcond)(ite))(ifTrue, ifFalse)
248+
}
217249
}
218250

219251
object Apply {

core/src/main/scala/cats/Bitraverse.scala

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package cats
22

3-
import simulacrum.typeclass
3+
import simulacrum.{noop, typeclass}
44

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

6262
override def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D] =
6363
bitraverse[Id, A, B, C, D](fab)(f, g)
64+
65+
/**
66+
* Traverse over the left side of the structure.
67+
* For the right side, use the standard `traverse` from [[cats.Traverse]].
68+
*
69+
* Example:
70+
* {{{
71+
* scala> import cats.implicits._
72+
*
73+
* scala> val intAndString: (Int, String) = (7, "test")
74+
*
75+
* scala> Bitraverse[Tuple2].leftTraverse(intAndString)(i => Option(i).filter(_ > 5))
76+
* res1: Option[(Int, String)] = Some((7,test))
77+
*
78+
* scala> Bitraverse[Tuple2].leftTraverse(intAndString)(i => Option(i).filter(_ < 5))
79+
* res2: Option[(Int, String)] = None
80+
* }}}
81+
*/
82+
@noop
83+
def leftTraverse[G[_], A, B, C](fab: F[A, B])(f: A => G[C])(implicit G: Applicative[G]): G[F[C, B]] =
84+
bitraverse(fab)(f, G.pure(_))
85+
86+
/**
87+
* Sequence the left side of the structure.
88+
* For the right side, use the standard `sequence` from [[cats.Traverse]].
89+
*
90+
* Example:
91+
* {{{
92+
* scala> import cats.implicits._
93+
*
94+
* scala> val optionalErrorRight: Either[Option[String], Int] = Either.right(123)
95+
* scala> optionalErrorRight.leftSequence
96+
* res1: Option[Either[String, Int]] = Some(Right(123))
97+
*
98+
* scala> val optionalErrorLeftSome: Either[Option[String], Int] = Either.left(Some("something went wrong"))
99+
* scala> optionalErrorLeftSome.leftSequence
100+
* res2: Option[Either[String, Int]] = Some(Left(something went wrong))
101+
*
102+
* scala> val optionalErrorLeftNone: Either[Option[String], Int] = Either.left(None)
103+
* scala> optionalErrorLeftNone.leftSequence
104+
* res3: Option[Either[String,Int]] = None
105+
* }}}
106+
*/
107+
@noop
108+
def leftSequence[G[_], A, B](fgab: F[G[A], B])(implicit G: Applicative[G]): G[F[A, B]] =
109+
bitraverse(fgab)(identity, G.pure(_))
64110
}
65111

66112
private[cats] trait ComposedBitraverse[F[_, _], G[_, _]]

core/src/main/scala/cats/FlatMap.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,50 @@ import simulacrum.noop
149149
*/
150150
def flatTap[A, B](fa: F[A])(f: A => F[B]): F[A] =
151151
flatMap(fa)(a => as(f(a), a))
152+
153+
/**
154+
* Like an infinite loop of >> calls. This is most useful effect loops
155+
* that you want to run forever in for instance a server.
156+
*
157+
* This will be an infinite loop, or it will return an F[Nothing].
158+
*
159+
* Be careful using this.
160+
* For instance, a List of length k will produce a list of length k^n at iteration
161+
* n. This means if k = 0, we return an empty list, if k = 1, we loop forever
162+
* allocating single element lists, but if we have a k > 1, we will allocate
163+
* exponentially increasing memory and very quickly OOM.
164+
*/
165+
@noop
166+
def foreverM[A, B](fa: F[A]): F[B] = {
167+
// allocate two things once for efficiency.
168+
val leftUnit = Left(())
169+
val stepResult: F[Either[Unit, B]] = map(fa)(_ => leftUnit)
170+
tailRecM(())(_ => stepResult)
171+
}
172+
173+
/**
174+
* iterateForeverM is almost exclusively useful for effect types. For instance,
175+
* A may be some state, we may take the current state, run some effect to get
176+
* a new state and repeat.
177+
*/
178+
@noop
179+
def iterateForeverM[A, B](a: A)(f: A => F[A]): F[B] =
180+
tailRecM[A, B](a)(f.andThen { fa =>
181+
map(fa)(Left(_): Either[A, B])
182+
})
183+
184+
/**
185+
* This repeats an F until we get defined values. This can be useful
186+
* for polling type operations on State (or RNG) Monads, or in effect
187+
* monads.
188+
*/
189+
@noop
190+
def untilDefinedM[A](foa: F[Option[A]]): F[A] = {
191+
val leftUnit: Either[Unit, A] = Left(())
192+
val feither: F[Either[Unit, A]] = map(foa) {
193+
case None => leftUnit
194+
case Some(a) => Right(a)
195+
}
196+
tailRecM(())(_ => feither)
197+
}
152198
}

0 commit comments

Comments
 (0)