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

Add Raise#fromEither and friends #514

Merged
merged 3 commits into from
Feb 2, 2024
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
7 changes: 6 additions & 1 deletion core/src/main/scala/cats/mtl/Ask.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package cats
package mtl

import cats.data.{Kleisli, ReaderWriterStateT => RWST}
import cats.data.{Kleisli, Reader, ReaderWriterStateT => RWST}
import cats.mtl.Ask.{const, AskImpl}
import cats.syntax.all._

Expand Down Expand Up @@ -51,6 +51,11 @@ trait Ask[F[_], +E] extends Serializable {
def ask[E2 >: E]: F[E2]

def reader[A](f: E => A): F[A] = applicative.map(ask)(f)

def fromReader[A](ra: Reader[E, A]): F[A] = reader(ra.run)

def fromKleisli[A](ka: Kleisli[F, E, A])(implicit F: FlatMap[F]): F[A] =
ask.flatMap(ka.run(_))
Comment on lines +57 to +58
Copy link
Member Author

Choose a reason for hiding this comment

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

Andrew raised a great question about use-cases, which made me realize we can do even better 🙂

For example suppose you have a Ask[IO, Request[IO]] and you want to interop with an http4s thing Kleisli[IO, Request[IO], Response[IO]]. Well, now you can!

}

private[mtl] trait AskForMonadPartialOrder[F[_], G[_], E] extends Ask[G, E] {
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/mtl/Raise.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ trait Raise[F[_], -E] extends Serializable {
def ensure[E2 <: E, A](fa: F[A])(error: => E2)(predicate: A => Boolean)(
implicit A: Monad[F]): F[A] =
A.flatMap(fa)(a => if (predicate(a)) A.pure(a) else raise(error))

def fromEither[A](ea: Either[E, A])(implicit F: Applicative[F]): F[A] =
ea.fold(raise, F.pure)

def fromEitherT[E2 <: E, A](ea: EitherT[F, E2, A])(implicit F: Monad[F]): F[A] =
F.flatMap(ea.value)(fromEither(_))

armanbilge marked this conversation as resolved.
Show resolved Hide resolved
def fromOption[E2 <: E, A](oa: Option[A])(e: => E2)(implicit F: Applicative[F]): F[A] =
oa.fold[F[A]](raise(e))(F.pure)

def fromOptionT[E2 <: E, A](ota: OptionT[F, A])(e: => E2)(implicit F: Monad[F]): F[A] =
F.flatMap(ota.value)(oa => fromOption(oa)(e))

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def fromValidated[E2 <: E, A](va: Validated[E2, A])(implicit F: Applicative[F]): F[A] =
va.fold(raise, F.pure)

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we are not accidentally adding a performance penalty comparing with, for example:

  def fromValidated[E2 <: E, A](va: Validated[E2, A])(implicit F: Applicative[F]): F[A] =
    va match {
      case Validated.Invalid(e) => raise(e)
      case Validated.Valid(a) => F.pure(a)
    }

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually we are. Using fold requires allocating lambdas, pattern matching does not. I don't think it's important in practice.

}

private[mtl] trait RaiseMonadPartialOrder[F[_], G[_], E] extends Raise[G, E] {
Expand Down
15 changes: 14 additions & 1 deletion core/src/main/scala/cats/mtl/Stateful.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package cats
package mtl

import cats.data.{ReaderWriterStateT => RWST, StateT}
import cats.data.{ReaderWriterStateT => RWST, State, StateT}

import scala.annotation.implicitNotFound

Expand Down Expand Up @@ -64,6 +64,19 @@ trait Stateful[F[_], S] extends Serializable {
def get: F[S]

def set(s: S): F[Unit]

def fromState[A](state: State[S, A]): F[A] =
monad.flatMap(get) { s0 =>
val (s1, a) = state.run(s0).value
monad.as(set(s1), a)
}

def fromStateT[A](state: StateT[F, S, A]): F[A] =
monad.flatMap(get) { s0 =>
monad.flatMap(state.run(s0)(monad)) { case (s1, a) =>
monad.as(set(s1), a)
}
}
}

private[mtl] trait LowPriorityStatefulInstances {
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/scala/cats/mtl/Tell.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package cats
package mtl

import cats.data.{ReaderWriterStateT => RWST, WriterT}
import cats.data.{ReaderWriterStateT => RWST, Writer, WriterT}

import scala.annotation.implicitNotFound

Expand Down Expand Up @@ -47,6 +47,11 @@ trait Tell[F[_], -L] extends Serializable {
def writer[A](a: A, l: L): F[A] = functor.as(tell(l), a)

def tuple[A](ta: (L, A)): F[A] = writer(ta._2, ta._1)

def fromWriter[L2 <: L, A](wa: Writer[L2, A]): F[A] = tuple(wa.run)

def fromWriterT[L2 <: L, A](wa: WriterT[F, L2, A])(implicit F: FlatMap[F]): F[A] =
F.flatMap(wa.run)(tuple(_))
}

private[mtl] trait TellMonadPartialOrder[F[_], G[_], L] extends Tell[G, L] {
Expand Down