Created
September 6, 2020 13:34
-
-
Save btlines/9cd6864ca9f437b90b843bc63b30a145 to your computer and use it in GitHub Desktop.
A better Future
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package effects | |
import cats.{MonadError, StackSafeMonad} | |
import scala.concurrent.{ExecutionContext, Future} | |
import scala.util.{Failure, Success, Try} | |
final case class F[+E, +A](value: Future[Either[E, A]]) extends AnyVal { | |
def fold[B](fe: E => B, fa: A => B)(implicit ec: ExecutionContext): Future[B] = value.map { | |
case Right(a) => fa(a) | |
case Left(e) => fe(e) | |
} | |
def toOption(implicit ec: ExecutionContext): Future[Option[A]] = value.map(_.toOption) | |
def swap(implicit ec: ExecutionContext): F[A, E] = F(value.map(_.swap)) | |
def getOrElse[B >: A](default: => B)(implicit ec: ExecutionContext): Future[B] = | |
fold(_ => default, identity) | |
def orElse[EE, B >: A](default: => F[EE, B])(implicit ec: ExecutionContext): F[EE, B] = | |
F(value.flatMap { | |
case Right(a) => Future.successful(Right(a)) | |
case Left(_) => default.value | |
}) | |
def handleError[B >: A](f: E => B)(implicit ec: ExecutionContext): F[Nothing, B] = | |
F(value.map { | |
case Left(e) => Right(f(e)) | |
case Right(a) => Right(a) | |
}) | |
def handleErrorWith[EE, B >: A](f: E => F[EE, B])(implicit e: ExecutionContext): F[EE, B] = | |
F(value.flatMap { | |
case Right(a) => Future.successful(Right(a)) | |
case Left(e) => f(e).value | |
}) | |
def recover[B >: A](pf: PartialFunction[E, B])(implicit ec: ExecutionContext): F[E, B] = | |
F(value.map { | |
case Right(a) => Right(a) | |
case Left(e) if pf.isDefinedAt(e) => Right(pf(e)) | |
case Left(e) => Left(e) | |
}) | |
def recoverWith[EE >: E, B >: A](pf: PartialFunction[E, F[EE, B]])(implicit e: ExecutionContext): F[EE, B] = | |
F(value.flatMap { | |
case Right(a) => Future.successful(Right(a)) | |
case Left(e) if pf.isDefinedAt(e) => pf(e).value | |
case Left(e) => Future.successful(Left(e)) | |
}) | |
def collect[EE >: E, B](e: => EE)(pf: PartialFunction[A, B])(implicit ec: ExecutionContext): F[EE, B] = | |
F(value.map { | |
case Right(a) if pf.isDefinedAt(a) => Right(pf(a)) | |
case Right(_) => Left(e) | |
case Left(e) => Left(e) | |
}) | |
def forall(f: A => Boolean)(implicit ec: ExecutionContext): Future[Boolean] = value.map(_.forall(f)) | |
def exists(f: A => Boolean)(implicit ec: ExecutionContext): Future[Boolean] = value.map(_.exists(f)) | |
def foreach(f: A => Unit)(implicit ec: ExecutionContext): F[E, A] = | |
flatMap { a => F.success(f(a)).as(a) } | |
def foreachError(f: E => Unit)(implicit ec: ExecutionContext): F[E, A] = | |
swap.foreach(f).swap | |
def attempt(implicit ec: ExecutionContext): F[Nothing, Either[E, A]] = transform(Right(_)) | |
def ensure[EE >: E](f: A => Boolean, e: => EE)(implicit ec: ExecutionContext): F[EE, A] = | |
F(value.map { | |
case Right(a) if f(a) => Right(a) | |
case Right(_) => Left(e) | |
case Left(e) => Left(e) | |
}) | |
def ensure[EE >: E](f: A => Boolean)(fe: A => EE)(implicit ec: ExecutionContext): F[EE, A] = | |
F(value.map { | |
case Right(a) if f(a) => Right(a) | |
case Right(a) => Left(fe(a)) | |
case Left(e) => Left(e) | |
}) | |
def flatMap[EE >: E, B](f: A => F[EE, B])(implicit ec: ExecutionContext): F[EE, B] = | |
F(value.flatMap { | |
case Right(a) => f(a).value.map { | |
case Left(e) => Left(e) | |
case Right(a) => Right(a) | |
} | |
case Left(e) => Future.successful(Left(e)) | |
}) | |
def map[B](f: A => B)(implicit ec: ExecutionContext): F[E, B] = flatMap(a => F.success(f(a))) | |
def as[B](b: => B)(implicit ec: ExecutionContext): F[E, B] = map(_ => b) | |
def void(implicit ec: ExecutionContext): F[E, Unit] = as(()) | |
def transform[EE, B](f: Either[E, A] => Either[EE, B])(implicit ec: ExecutionContext): F[EE, B] = | |
F(value.map(f)) | |
} | |
object F extends FInstances { | |
val unit: F[Nothing, Unit] = F.success(()) | |
def success[A](a: => A): F[Nothing, A] = F(Future.fromTry(Try(Right(a)))) | |
def fail[E](e: => E): F[E, Nothing] = F(Future.fromTry(Try(Left(e)))) | |
def fromFuture[A](fa: => Future[A])(implicit ec: ExecutionContext): F[Throwable, A] = | |
F(Future.fromTry(Try(fa)).flatten.transform { | |
case Success(a) => Success(Right(a)) | |
case Failure(e) => Success(Left(e)) | |
}) | |
def fromEither[E, A](v: => Either[E, A]): F[E, A] = F(Future.fromTry(Try(v))) | |
def fromTry[A](ta: => Try[A]): F[Throwable, A] = F(Future.fromTry(Try(ta.toEither))) | |
def catchAll[A](a: => A): F[Throwable, A] = F.fromTry(Try(a)) | |
def ensure[E](t: Boolean)(e: => E): F[E, Unit] = if (t) F.unit else F.fail(e) | |
def when[E](t: Boolean)(fa: => F[E, Unit]): F[E, Unit] = | |
if (t) fa else F.unit | |
} | |
sealed abstract class FInstances { | |
implicit def monadError[E](implicit ec: ExecutionContext): MonadError[({type M[A] = F[E, A]})#M, E] = | |
new MonadError[({type M[A] = F[E, A]})#M, E] with StackSafeMonad[({type M[A] = F[E, A]})#M] { | |
def pure[A](a: A): F[E, A] = F.success(a) | |
def handleErrorWith[A](fa: F[E, A])(f: E => F[E, A]): F[E, A] = | |
fa.handleErrorWith(f) | |
def raiseError[A](e: E): F[E, A] = F.fail(e) | |
def flatMap[A, B](fa: F[E, A])(f: A => F[E, B]): F[E,B] = fa.flatMap(f) | |
} | |
implicit final class Flattenable[E, EE >: E, A](ff: => F[E, F[E, A]]) { | |
def flatten(implicit ec: ExecutionContext): F[E, A] = ff.flatMap(identity) | |
} | |
implicit final class Mergeable[A](f: => F[A, A]) { | |
def merge(implicit ec: ExecutionContext): F[Nothing, A] = f.attempt.map(_.merge) | |
} | |
implicit final class Gettable[A](f: => F[Nothing, A]) { | |
def get(implicit ec: ExecutionContext): Future[A] = f.value.map(_.right.get) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment