All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.catbird.util.future.scala Maven / Gradle / Ivy

The newest version!
package io.catbird.util

import cats.{
  Applicative,
  CoflatMap,
  CommutativeApplicative,
  Comonad,
  Eq,
  Monad,
  MonadError,
  Monoid,
  Parallel,
  Semigroup,
  ~>
}
import com.twitter.util.{ Await, Duration, Future, Try }
import java.lang.Throwable

import scala.Boolean
import scala.util.{ Either, Left, Right }

trait FutureInstances extends FutureInstances1 {
  implicit final val twitterFutureInstance: MonadError[Future, Throwable] with CoflatMap[Future] =
    new FutureCoflatMap with FutureMonadError

  implicit final def twitterFutureSemigroup[A](implicit A: Semigroup[A]): Semigroup[Future[A]] =
    new FutureSemigroup[A]

  /**
   * Obtain a [[cats.Eq]] instance for [[com.twitter.util.Future]].
   *
   * This version is only useful for successful futures: if one of the futures fails, the resulting exception
   * will be thrown.
   *
   * These instances use [[com.twitter.util.Await]] so should be
   * [[https://finagle.github.io/blog/2016/09/01/block-party/ avoided in production code]].  Likely use cases
   * include tests, scrips, REPLs etc.
   */
  final def futureEq[A](atMost: Duration)(implicit A: Eq[A]): Eq[Future[A]] = new Eq[Future[A]] {
    final def eqv(x: Future[A], y: Future[A]): Boolean = Await.result(
      x.join(y).map { case (xa, ya) =>
        A.eqv(xa, ya)
      },
      atMost
    )
  }

  /**
   * Obtain a [[cats.Eq]] instance for [[com.twitter.util.Future]].
   *
   * This version can also compare failed futures and thus requires an `Eq[Throwable]` in scope.
   *
   * These instances use [[com.twitter.util.Await]] so should be
   * [[https://finagle.github.io/blog/2016/09/01/block-party/ avoided in production code]].  Likely use cases
   * include tests, scrips, REPLs etc.
   */
  final def futureEqWithFailure[A](atMost: Duration)(implicit A: Eq[A], T: Eq[Throwable]): Eq[Future[A]] =
    Eq.by[Future[A], Future[Try[A]]](_.liftToTry)(futureEq[Try[A]](atMost))

  implicit final val twitterFutureParallelInstance: Parallel.Aux[Future, FuturePar] =
    new Parallel[Future] {
      type F[x] = FuturePar[x]

      final override val applicative: Applicative[FuturePar] =
        futureParCommutativeApplicative

      final override val monad: Monad[Future] =
        twitterFutureInstance

      final override val sequential: FuturePar ~> Future = λ[FuturePar ~> Future](FuturePar.unwrap(_))

      final override val parallel: Future ~> FuturePar = λ[Future ~> FuturePar](FuturePar(_))
    }
}

private[util] trait FutureInstances1 extends FutureParallelNewtype {

  /**
   * Obtain a [[cats.Comonad]] instance for [[com.twitter.util.Future]].
   *
   * These instances use [[com.twitter.util.Await]] so should be
   * [[https://finagle.github.io/blog/2016/09/01/block-party/ avoided in production code]].  Likely use cases
   * include tests, scrips, REPLs etc.
   */
  final def futureComonad(atMost: Duration): Comonad[Future] =
    new FutureCoflatMap with Comonad[Future] {
      final def extract[A](x: Future[A]): A = Await.result(x, atMost)
      final def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f)
    }

  implicit final def twitterFutureMonoid[A](implicit A: Monoid[A]): Monoid[Future[A]] =
    new FutureSemigroup[A] with Monoid[Future[A]] {
      final def empty: Future[A] = Future.value(A.empty)
    }
}

private[util] trait FutureParallelNewtype {
  type FuturePar[+A] = FuturePar.Type[A]

  object FuturePar extends internal.Newtype1[Future]

  implicit final val futureParCommutativeApplicative: CommutativeApplicative[FuturePar] =
    new CommutativeApplicative[FuturePar] {
      import FuturePar.{ unwrap, apply => par }

      final override def pure[A](x: A): FuturePar[A] =
        par(Future.value(x))
      final override def map2[A, B, Z](fa: FuturePar[A], fb: FuturePar[B])(f: (A, B) => Z): FuturePar[Z] =
        par(Future.join(unwrap(fa), unwrap(fb)).map(f.tupled)) // Future.join runs futures in parallel
      final override def ap[A, B](ff: FuturePar[A => B])(fa: FuturePar[A]): FuturePar[B] =
        map2(ff, fa)(_(_))
      final override def product[A, B](fa: FuturePar[A], fb: FuturePar[B]): FuturePar[(A, B)] =
        map2(fa, fb)((_, _))
      final override def map[A, B](fa: FuturePar[A])(f: A => B): FuturePar[B] =
        par(unwrap(fa).map(f))
      final override def unit: FuturePar[scala.Unit] =
        par(Future.Unit)
    }

  final def futureParEq[A](atMost: Duration)(implicit A: Eq[A]): Eq[FuturePar[A]] = new Eq[FuturePar[A]] {
    import FuturePar.unwrap

    final override def eqv(x: FuturePar[A], y: FuturePar[A]): Boolean =
      futureEq(atMost)(A).eqv(unwrap(x), unwrap(y))
  }

  final def futureParEqWithFailure[A](atMost: Duration)(implicit A: Eq[A], T: Eq[Throwable]): Eq[FuturePar[A]] =
    new Eq[FuturePar[A]] {
      import FuturePar.unwrap

      final override def eqv(x: FuturePar[A], y: FuturePar[A]): Boolean =
        futureEqWithFailure(atMost)(A, T).eqv(unwrap(x), unwrap(y))
    }
}

private[util] trait FutureMonadError extends MonadError[Future, Throwable] {
  final def pure[A](x: A): Future[A] = Future.value(x)
  final def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f)
  override final def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f)
  override final def ap[A, B](f: Future[A => B])(fa: Future[A]): Future[B] = f.join(fa).map { case (ab, a) =>
    ab(a)
  }
  override final def product[A, B](fa: Future[A], fb: Future[B]): Future[(A, B)] = fa.join(fb)

  final def handleErrorWith[A](fa: Future[A])(f: Throwable => Future[A]): Future[A] =
    fa.rescue { case e =>
      f(e)
    }
  final def raiseError[A](e: Throwable): Future[A] = Future.exception(e)

  final def tailRecM[A, B](a: A)(f: A => Future[Either[A, B]]): Future[B] = f(a).flatMap {
    case Right(b)    => pure(b)
    case Left(nextA) => tailRecM(nextA)(f)
  }
}

private[util] sealed abstract class FutureCoflatMap extends CoflatMap[Future] {
  final def coflatMap[A, B](fa: Future[A])(f: Future[A] => B): Future[B] = Future(f(fa))
}

private[util] sealed class FutureSemigroup[A](implicit A: Semigroup[A]) extends Semigroup[Future[A]] {
  final def combine(fx: Future[A], fy: Future[A]): Future[A] = fx.join(fy).map { case (x, y) =>
    A.combine(x, y)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy