Skip to content

Instantly share code, notes, and snippets.

@btlines
Created September 6, 2020 13:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save btlines/9cd6864ca9f437b90b843bc63b30a145 to your computer and use it in GitHub Desktop.
Save btlines/9cd6864ca9f437b90b843bc63b30a145 to your computer and use it in GitHub Desktop.
A better Future
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