如何从Async [IO]创建Async [Future]

时间:2019-05-03 10:37:14

标签: scala scala-cats doobie

我正在尝试在我的doobie存储库代码中隐式添加Async和Sync。 Sync和Async [F]可以正常工作IO。我想将它们转换为未来并面临问题

我试图通过IO创建自己的Aync

def futureAsync(implicit F: MonadError[Future, Throwable]): Async[Future] = new Async[Future] {
    override def async[A](k: (Either[Throwable, A] => Unit) => Unit): Future[A] = IO.async(k).unsafeToFuture()

    override def asyncF[A](k: (Either[Throwable, A] => Unit) => Future[Unit]): Future[A] =
      throw new Exception("Not implemented Future.asyncF")

    override def suspend[A](thunk: => Future[A]): Future[A] = thunk

    override def bracketCase[A, B](acquire: Future[A])(use: A => Future[B])(release: (A, ExitCase[Throwable]) => Future[Unit]): Future[B] =
      throw new Exception("Not implemented Future.bracketCase")

    override def raiseError[A](e: Throwable): Future[A] = F.raiseError(e)

    override def handleErrorWith[A](fa: Future[A])(f: Throwable => Future[A]): Future[A] = F.handleErrorWith(fa)(_ => f(new Exception("")))

    override def pure[A](x: A): Future[A] = F.pure(x)

    override def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = F.flatMap(fa)(f)

    override def tailRecM[A, B](a: A)(f: A => Future[Either[A, B]]): Future[B] = F.tailRecM(a)(f)
  }

我对asyncF和bracketCase中的两个函数的实现感到震惊 有人可以帮忙吗?

1 个答案:

答案 0 :(得分:12)

正如Reactormonk在上面的评论中所说,不可能为<select class="form-control border custom-select" [(ngModel)]="category"> <option selected disabled="disabled" [value]="0">Chose a category...</option> <option *ngFor="let cat of categories" [value]="cat.id">{{cat.name}}</option> </select> 编写具有正确语义的Async实例,因为Future扩展了Async,并且Sync需要可以重复运行的计算的表示形式,而Scala的期货在定义后就可以开始运行,并且无法重新运行。

非法实例

不过,亲自了解这一点很有启发性,我鼓励您尝试编写自己的可编译但(必要时)非法的Sync实例,而不用看下一段代码。不过,出于示例的目的,这是我脑海中的一个快速草图:

Async[Future]

这将编译得很好,并且可能在某些情况下可以工作(但请不要实际使用它!)。不过,我们已经说过它没有正确的语义,我们可以通过使用cats-effect的laws模块来证明这一点。

检查法律

首先,我们需要一些您不需要真正担心的样东西:

import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.{Failure, Success}
import cats.effect.{Async, ExitCase, IO}

def futureAsync(implicit c: ExecutionContext): Async[Future] = new Async[Future] {
  def async[A](k: (Either[Throwable, A] => Unit) => Unit): Future[A] =
    IO.async(k).unsafeToFuture()

  def asyncF[A](k: (Either[Throwable, A] => Unit) => Future[Unit]): Future[A] = {
    val p = Promise[A]()
    val f = k {
      case Right(a) => p.success(a)
      case Left(e) => p.failure(e)
    }
    f.flatMap(_ => p.future)
  }

  def suspend[A](thunk: => Future[A]): Future[A] = Future(thunk).flatten

  def bracketCase[A, B](acquire: Future[A])(use: A => Future[B])(
    release: (A, ExitCase[Throwable]) => Future[Unit]
  ): Future[B] = acquire.flatMap { a =>
    use(a).transformWith {
      case Success(b) => release(a, ExitCase.Completed).map(_ => b)
      case Failure(e) => release(a, ExitCase.Error(e)).flatMap(_ => Future.failed(e))
    }
  }

  def raiseError[A](e: Throwable): Future[A] = Future.failed(e)
  def handleErrorWith[A](fa: Future[A])(f: Throwable => Future[A]): Future[A] =
    fa.recoverWith { case t => f(t) }

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

然后,我们可以定义一个测试来检查实例的import cats.kernel.Eq, cats.implicits._ import org.scalacheck.Arbitrary implicit val throwableEq: Eq[Throwable] = Eq.by[Throwable, String](_.toString) implicit val nonFatalArbitrary: Arbitrary[Throwable] = Arbitrary(Arbitrary.arbitrary[Exception].map(identity)) implicit def futureEq[A](implicit A: Eq[A], ec: ExecutionContext): Eq[Future[A]] = new Eq[Future[A]] { private def liftToEither(f: Future[A]): Future[Either[Throwable, A]] = f.map(Right(_)).recover { case e => Left(e) } def eqv(fx: Future[A], fy: Future[A]): Boolean = scala.concurrent.Await.result( liftToEither(fx).zip(liftToEither(fy)).map { case (rx, ry) => rx === ry }, scala.concurrent.duration.Duration(1, "second") ) } 律:

Async

然后我们可以进行法律测试:

import cats.effect.laws.discipline.{AsyncTests, Parameters}
import org.scalatest.FunSuite
import org.typelevel.discipline.scalatest.Discipline

object FutureAsyncSuite extends FunSuite with Discipline {
  implicit val ec: ExecutionContext = ExecutionContext.global

  implicit val params: Parameters =
    Parameters.default.copy(allowNonTerminationLaws = false)

  checkAll(
    "Async",
    AsyncTests[Future](futureAsync).async[String, String, String]
  )
}

您将看到大多数测试是绿色的;这个实例可以解决很多问题。

违反法律的地方

它确实显示了三个失败的测试,包括以下内容:

scala> FutureAsyncSuite.execute()
FutureAsyncSuite:
- Async.async.acquire and release of bracket are uncancelable
- Async.async.ap consistent with product + map
- Async.async.applicative homomorphism
...

如果您查看laws definitions,将会看到这是一个测试,它使用- Async.async.repeated sync evaluation not memoized *** FAILED *** GeneratorDrivenPropertyCheckFailedException was thrown during property evaluation. (Discipline.scala:14) Falsified after 1 successful property evaluations. Location: (Discipline.scala:14) Occurred when passed generated values ( arg0 = "淳칇멀", arg1 = org.scalacheck.GenArities$$Lambda$7154/1834868832@1624ea25 ) Label of failing property: Expected: Future(Success(驅ṇ숆㽝珅뢈矉)) Received: Future(Success(淳칇멀)) 定义了Future值,然后对其进行了多次排序,如下所示:

delay

其他两个失败是类似的“未记录”违规。这些测试应该看到副作用发生了两次,但是在我们的例子中,不可能以val change = F.delay { /* observable side effect here */ } val read = F.delay(cur) change *> change *> read 的方式写delaysuspend的方式(尽管值得尝试) ,说服自己就是这种情况。

您应该怎么做

总结:您可以编写一个Future实例,该实例将通过78个Async[Future]法则测试中的75个,但是不可能编写一个实例通过所有测试,并且使用非法实例是一个非常糟糕的主意:您的代码潜在用户和类似Doobie的库都将假设您的实例是合法的,如果您不遵守该假设,则可能会打开复杂而烦人的错误之门

值得注意的是,为具有合法Async实例的Future编写一个最小的包装并不是很困难(例如,我为Twitter的未来开发了一个名为Async的包装在我的catbird库中)。不过,您实际上应该只坚持使用Rerunnable,并使用提供的转换在使用传统的基于cats.effect.IO的API的代码的任何部分之间来回转换。