Created
April 28, 2021 18:29
-
-
Save btlines/d9699fd174b6fc1b11c33826739a9009 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import cats.{MonadError, StackSafeMonad} | |
import scala.concurrent.{ExecutionContext, Future} | |
import scala.util.{Failure, Success, Try} | |
package object effects { | |
type F[E, A] = ExecutionContext => Future[Either[E, A]] | |
object F { | |
val unit: F[Nothing, Unit] = F.success(()) | |
def success[A](a: => A): F[Nothing, A] = (_: ExecutionContext) => Future.fromTry(Try(Right(a))) | |
def fail[E](e: => E): F[E, Nothing] = (_: ExecutionContext) => Future.fromTry(Try(Left(e))) | |
def fromFuture[A](fa: => Future[A]): F[Throwable, A] = (ec: ExecutionContext) => | |
Future | |
.fromTry(Try(fa)) | |
.flatten | |
.transform { | |
case Success(a) => Success(Right(a)) | |
case Failure(e) => Success(Left(e)) | |
}(ec) | |
def fromEither[E, A](res: => Either[E, A]): F[E, A] = (_: ExecutionContext) => Future.fromTry(Try(res)) | |
def fromTry[A](t: => Try[A]): F[Throwable, A] = (_: ExecutionContext) => Future.fromTry(Try(t.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 | |
} | |
implicit final class FOps[E, A](val run: F[E, A]) extends AnyVal { | |
def execute(implicit ec: ExecutionContext): Future[Either[E, A]] = run(ec) | |
def on(ec: ExecutionContext) = (_: ExecutionContext) => run(ec) | |
def fold[B](fe: E => B, fa: A => B): F[Nothing, B] = | |
(ec: ExecutionContext) => | |
run(ec).map { | |
case Right(a) => Right(fa(a)) | |
case Left(e) => Right(fe(e)) | |
}(ec) | |
def toOption: F[Nothing, Option[A]] = (ec: ExecutionContext) => run(ec).map(res => Right(res.toOption))(ec) | |
def swap: F[A, E] = (ec: ExecutionContext) => run(ec).map(_.swap)(ec) | |
def mapError[B](fe: E => B): F[B, A] = | |
(ec: ExecutionContext) => | |
run(ec).map { | |
case Left(e) => Left(fe(e)) | |
case Right(a) => Right(a) | |
}(ec) | |
def orElse[EE, B >: A](default: => F[EE, B]): F[EE, B] = | |
(ec: ExecutionContext) => | |
run(ec).flatMap { | |
case Right(a) => Future.successful(Right(a)) | |
case Left(_) => default(ec) | |
}(ec) | |
def handleError[B >: A](f: E => B): F[Nothing, B] = | |
(ec: ExecutionContext) => | |
run(ec).map { | |
case Left(e) => Right(f(e)) | |
case Right(a) => Right(a) | |
}(ec) | |
def handleErrorWith[EE, B >: A](f: E => F[EE, B]): F[EE, B] = | |
(ec: ExecutionContext) => | |
run(ec).flatMap { | |
case Right(a) => Future.successful(Right(a)) | |
case Left(e) => f(e)(ec) | |
}(ec) | |
def recover[B >: A](pf: PartialFunction[E, B]): F[E, B] = | |
(ec: ExecutionContext) => | |
run(ec).map { | |
case Left(e) if pf.isDefinedAt(e) => Right(pf(e)) | |
case Right(a) => Right(a) | |
case Left(e) => Left(e) | |
}(ec) | |
def recoverWith[EE >: E, B >: A](pf: PartialFunction[E, F[EE, B]]): F[EE, B] = | |
(ec: ExecutionContext) => | |
run(ec).flatMap { | |
case Left(e) if pf.isDefinedAt(e) => pf(e)(ec) | |
case Right(a) => Future.successful(Right(a)) | |
case Left(e) => Future.successful(Left(e)) | |
}(ec) | |
def collect[EE >: E, B](e: => EE)(pf: PartialFunction[A, B]): F[EE, B] = | |
(ec: ExecutionContext) => | |
run(ec).map { | |
case Right(a) if pf.isDefinedAt(a) => Right(pf(a)) | |
case Right(_) => Left(e) | |
case Left(e) => Left(e) | |
}(ec) | |
def forall(f: A => Boolean): F[Nothing, Boolean] = (ec: ExecutionContext) => run(ec).map(res => Right(res.forall(f)))(ec) | |
def exists(f: A => Boolean): F[Nothing, Boolean] = (ec: ExecutionContext) => run(ec).map(res => Right(res.exists(f)))(ec) | |
def foreach(f: A => Unit): F[E, A] = flatMap(a => F.success(f(a)).as(a)) | |
def foreachError(f: E => Unit): F[E, A] = swap.foreach(f).swap | |
def attempt: F[Nothing, Either[E, A]] = transform(Right(_)) | |
def ensure[EE >: E](f: A => Boolean, e: => EE): F[EE, A] = | |
(ec: ExecutionContext) => | |
run(ec).map { | |
case Right(a) if f(a) => Right(a) | |
case Right(_) => Left(e) | |
case Left(e) => Left(e) | |
}(ec) | |
def ensure[EE >: E](f: A => Boolean)(fe: A => EE): F[EE, A] = | |
(ec: ExecutionContext) => | |
run(ec).map { | |
case Right(a) if f(a) => Right(a) | |
case Right(a) => Left(fe(a)) | |
case Left(e) => Left(e) | |
}(ec) | |
def flatMap[EE >: E, B](f: A => F[EE, B]): F[EE, B] = | |
(ec: ExecutionContext) => | |
run(ec).flatMap { | |
case Right(a) => | |
f(a)(ec).map { | |
case Left(e) => Left(e) | |
case Right(a) => Right(a) | |
}(ec) | |
case Left(e) => Future.successful(Left(e)) | |
}(ec) | |
def map[B](f: A => B): F[E, B] = flatMap(a => F.success(f(a))) | |
def as[B](b: => B): F[E, B] = map(_ => b) | |
def void: F[E, Unit] = as(()) | |
def transform[EE, B](f: Either[E, A] => Either[EE, B]): F[EE, B] = (ec: ExecutionContext) => run(ec).map(f)(ec) | |
def forever: F[E, Nothing] = run.flatMap(_ => forever) | |
def repeat(n: Int): F[E, A] = | |
if (n > 0) run.flatMap(_ => repeat(n - 1)) | |
else run | |
def retry(n: Int): F[E, A] = | |
if (n > 0) run.handleErrorWith(_ => retry(n - 1)) | |
else run | |
def retryForever: F[E, A] = run.handleErrorWith(_ => retryForever) | |
} | |
implicit final class Flattenable[E, A](val ff: F[E, F[E, A]]) extends AnyVal { | |
def flatten: F[E, A] = ff.flatMap(identity) | |
} | |
implicit final class Mergeable[A](val f: F[A, A]) extends AnyVal { | |
def merge: F[Nothing, A] = f.attempt.map(_.merge) | |
} | |
implicit final class Successful[A](val f: F[Nothing, A]) extends AnyVal { | |
def get(implicit ec: ExecutionContext): Future[A] = f(ec).map(_.right.get) | |
} | |
implicit final class Optionable[E, A](val f: F[E, Option[A]]) extends AnyVal { | |
def getOrElse[EE >: E](e: => EE): F[EE, A] = | |
f.flatMap { | |
case Some(a) => F.success(a) | |
case None => F.fail(e) | |
} | |
} | |
implicit final class Unitable[E](val f: F[E, Unit]) extends AnyVal { | |
def when(t: Boolean): F[E, Unit] = if (t) f else F.unit | |
} | |
implicit def monadError[E]: 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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment