Skip to content

Instantly share code, notes, and snippets.

@btlines
Created April 28, 2021 18:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save btlines/d9699fd174b6fc1b11c33826739a9009 to your computer and use it in GitHub Desktop.
Save btlines/d9699fd174b6fc1b11c33826739a9009 to your computer and use it in GitHub Desktop.
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