2.7 zio入门——更多的Effect构造函数

2.7 更多的Effect构造函数

在本章的前面,我们了解了如何使用ZIO.effect构造函数将过程代码转换为ZIO效果。ZIO.effect构造函数是一种有用且通用的效果构造函数,但并不适合所有情况:

  1. 易犯错误。 ZIO.effect构造函数返回的效果可能因任何Throwable类型(ZIO [Any,Throwable,A])而失败。 当您将遗留代码转换为ZIO并且不知道它是否会引发异常时,这是正确的选择,但是有时候,我们知道某些代码不会引发异常(例如检索系统时间)。
  2. 同步。 ZIO.effect构造函数要求我们的过程代码是同步的,并从捕获的代码块中返回指定类型的某些值。 但是在异步API中,我们必须注册一个回调,以便在类型A的值可用时调用。 我们如何将异步代码转换为ZIO效果?
  3. 展开。 ZIO.effect构造函数假定我们正在计算的值没有包装在另一个数据类型中,该数据类型具有自己的建模失败方式。 但是,我们与之交互的一些代码返回了Option [A]Either [E,A]Try [A]甚至是Future [A]。 我们如何从这些类型转换为ZIO效果?

幸运的是,ZIO有许多强大的构造函数,可自定义失败处理方案,异步代码和其他常见数据类型。

2.7.1 纯值与不纯值

在介绍其他ZIO effect构造函数之前,我们首先需要讨论参照透明性。如果我们始终可以在任何程序中将计算替换为其结果,同时仍保留其运行时行为,则诸如2 + 2之类的表达式将是透明的。
例如,考虑以下表达式:

val sum: Int = 2 + 2

我们可以将表达式2 + 2替换为其结果4,我们的程序的运行结果不会有任何改变。
相反,请考虑以下简单程序,该程序从控制台读取一行输入,然后将其输出到控制台:

  import scala.io.StdIn
  val echo: Unit = {
    val line = StdIn.readLine()
    println(line)
  }

我们无法将 echo 的代码块替换为其结果并维持程序的行为。 echo的结果值只是Unit值,因此,如果将echo替换为其返回值,则将具有:

val echo: Unit = ()

这两个程序肯定不一样。 第一个从用户读取输入,并将输入打印到控制台,但是第二个程序什么也不做!
我们无法用其计算结果代替echo的原因是它会产生副作用。 它在一边做事(从控制台读取和写入控制台)。 这与参照透明函数相反,后者没有副作用,仅计算值。没有副作用的表达式称为纯表达式,而主体为纯表达式的函数称为纯函数。ZIO.effect构造函数采用副作用代码,并将其转换为纯值,该值仅描述副作用。

为了了解这一点,让我们重新了解回声程序的ZIO实现:

  import zio._
  
  val readLine = ZIO.effect(StdIn.readLine())
  def printLine(line: String) = ZIO.effect(println(line))

  val echo = for {
    line <- readLine
    _ <- printLine(line)
  } yield ()

该程序是参照透明的,因为它仅建立了工作计划,而没有产生任何副作用。我们可以用生成的蓝图替换构建此蓝图的代码,而我们仍然对此echo程序有一个计划。
因此,我们可以将参照透明性作为另一种角度,去理解"函数式作用是工作蓝图"。函数式作用通过描述副作用而不是执行它们,从而使副作用代码具有参照透明性。
描述和执行之间的这种分离,解耦"怎么做"和"做什么"之间的混乱,并赋予我们巨大的力量来变换和合成effect,正如我们在本书中将会看到的那样。
参照透明性是将代码转换为ZIO时的一个重要概念,因为如果值或函数是参照透明的,则我们无需将其转换为ZIO effect。但是,如果不纯净,则需要使用正确的effect构造函数将其转换为ZIO效果。
即使您不小心将副作用代码视为纯代码,ZIO也会尝试做正确的事情。但是,将副作用代码与ZIO代码混合在一起可能会导致bug的产生,因此,最好谨慎使用正确的effect构造函数。作为附带的好处,这将使您的代码更易于同事阅读和查看。

2.7.2用于纯计算的effect构造器

These constructors are useful primarily when combining other ZIO effects, which have been constructed from side-effecting code, with pure code.
ZIO带有各种effect构造器,可将纯值转换为ZIO effect。 这些构造函数在和从其他副作用代码和纯代码构造的ZIO效果组合在一起时非常有用。
此外,即使是纯代码也可以从ZIO的某些功能中受益,例如环境,键入错误和堆栈安全性。
将纯值转换为ZIO效果的两种最基本的方法是成功和失败:

  object ZIO {
    def fail[E](e: => E): ZIO[Any, E, Nothing] = ???
    def succeed[A](a: => A): ZIO[Any, Nothing, A] = ???
  }
  • ZIO.succeed构造函数将一个值转换为以该值成功的effect。 例如,ZIO.succeed(42)构造一个以值42成功的effect。ZIO.succeed返回的效果的失败类型为Nothing,因为使用此构造函数创建的效果不会失败。
  • ZIO.fail构造函数将值转换为因该值而失败的效果。 例如,ZIO.fail(new Exception)构造一个因指定的异常而失败的效果。 ZIO.fail返回的效果的成功类型为Nothing,因为使用此构造函数创建的效果无法成功。
    我们将看到无法成功的效果,无论是因为它们失败还是因为它们永远运行,通常都将Nothing用作成功类型。
    除了这两个基本构造函数外,还有许多其他构造函数可以将标准Scala数据类型转换为ZIO效果。
  import scala.util.Try
  object ZIO {
    def fromEither[E, A](eea: => Either[E, A]): IO[E, A] = ??? 
    def fromOption[A](oa: => Option[A]): IO[None.type, A] = ??? 
    def fromTry[A](a: => Try[A]): Task[A] = ???
  }

这些构造函数将原始数据类型的成功和失败案例转换为ZIO成功和错误类型。
ZIO.fromEither构造函数将Either [E,A]转换为IO [E,A]效果。如果Either是Left,则生成的ZIO效果将以E失败,但是如果是Right,则生成的ZIO效果将以A成功。
ZIO.fromTry构造函数类似,不同之处在于错误类型固定为Throwable,因为Try只能通过Throwable失败。
ZIO.fromOption构造函数更有趣,它说明了一个经常出现的想法。请注意,错误类型为None.type。这是因为Option只有一种错误模式。 Option [A]是带有值的Some [A],或者是None,没有其他信息。
因此,一个Option可能会失败,但实际上只有一种可能会失败的方法-值为None。该唯一失败值的类型为None.type。
这些不是纯值唯一的效果构造函数。在本章结尾的练习中,您将探索其他一些构造函数。

2.7.3用于副作用计算的 effect 构造器

最重要的效果构造函数是用于副作用计算的那些。 这些构造函数将过程代码转换为ZIO effect,因此它们成为将内容与方法分开的蓝图。
在本章的前面,我们介绍了ZIO.effect。 该构造函数捕获有副作用的代码,并将其计算推迟到以后,将代码中引发的所有异常转换为ZIO.fail值。
但是,有时我们想将副作用代码转换为ZIO效果,但是我们知道副作用代码不会引发任何异常。 例如,检查系统时间或生成随机变量绝对是副作用,但是它们不能引发异常。
对于这些情况,我们可以使用构造函数ZIO.effectTotal,它将程序代码转换为不会失败的ZIO效果:

object ZIO {
  def effectTotal[A](a: => A): ZIO[Any, Nothing, A]
}

2.7.3.1 异步回调转换

JVM生态系统中的许多代码都是非阻塞的。 非阻塞代码不会同步计算并返回值。 相反,当您调用异步函数时,必须提供一个回调。然后,当该值可用时,将使用该值调用您的回调。 (有时这被隐藏在Future或其他异步数据类型的后面。)
例如,假设我们在以下代码段中显示了非阻塞查询API:

def getUserByIdAsync(id: Int)(cb: Option[String] => Unit): Unit = ???

如果我们给此函数一个我们感兴趣的id,它将在后台查找用户,但立即返回。 然后,在检索到用户之后,它将调用我们传递给该方法的回调函数。
在类型签名中使用Option表示没有用户使用我们请求的ID。
在以下代码段中,我们调用getUserByIdAsync,并传递一个回调,该回调将在接收到用户名时简单地打印出该用户名:

  getUserByIdAsync(0) {
    case Some(name) => println(name)
    case None => println("User not found!")
  }

请注意,对getUserByIdAsync的调用几乎会立即返回,即使在调用回调函数之前需要一段时间(甚至几秒钟或几分钟),并且实际上会将用户名打印到控制台上。
基于回调的API可以提高性能,因为我们可以编写更高效的代码而不会浪费线程。 但是直接使用基于回调的异步代码可能会很痛苦,导致代码高度嵌套,从而难以将成功和错误信息传播到正确的位置,并且无法安全地处理资源。
幸运的是,就像之前的Scala的Future一样,ZIO允许我们获取异步代码,并将其转换为ZIO功能效果。
我们需要执行此转换的构造函数是ZIO.effectAsync,其类型签名显示在以下片段中:

  object ZIO {
    def effectAsync[R, E, A](cb: (ZIO[R, E, A] => Unit) => Any): ZIO[R, E, A] =
      ???
  }

ZIO.effectAsync的类型签名可能很难理解,因此让我们来看一个示例。
要将getUserByIdAsync过程代码转换为ZIO,我们可以使用ZIO.effectAsync构造函数,如下所示:

  def getUserById(id: Int): ZIO[Any, None.type, String] = ZIO.effectAsync {
    callback =>
      getUserByIdAsync(id) {
        case Some(name) => callback(ZIO.succeed(name))
        case None => callback(ZIO.fail(None))
      }
  }

effectAsync提供的回调,并期望返回ZIO效果,因此,如果用户存在于数据库中,则我们使用ZIO.succeed将用户名转换为ZIO效果,然后使用此成功效果调用回调。另一方面,如果用户不存在,则使用ZIO.fail将None转换为ZIO效果,然后使用此失败的效果调用回调。
我们不得不花点时间将异步代码转换为ZIO函数,但是现在使用此查询API时,我们不再需要处理回调。现在,我们可以像对待任何其他ZIO函数一样对待getUserById,并使用flatMap之类的方法来组合其返回值,而这些函数都不会阻塞,并且具有ZIO为我们提供围绕资源安全性的所有保证。
一旦getUserById计算的结果可用,我们将继续创建的蓝图中的其他计算。
请注意,在effectAsync中,回调函数只能被调用一次,因此不适用于转换所有异步API。如果回调可能被调用多次,则可以在ZStream上使用effectAsync构造函数,这将在本书后面讨论。

我们将在本章中介绍的最后一个构造函数是ZIO.fromFuture,它将将创建Future的函数转换为ZIO效果。
此构造函数的类型签名如下:

def fromFuture[A](make: ExecutionContext => Future[A]): Task[A] = ???

因为Future是正在运行的计算,所以我们在执行此操作时必须非常小心。 fromFuture构造函数不需要Future。 相反,构造函数采用函数ExecutionContext => Future [A],该函数描述了如何在给定ExecutionContext的情况下制作Future。
尽管在将Future转换为ZIO效果时不需要使用提供的ExecutionContext,但是如果您使用上下文,则ZIO可以管理Future在更高级别上的运行位置。
如果可能,我们要确保我们的make函数实现创建一个新的Future,而不是返回已经运行的Future。 以下代码显示了执行此操作的示例:

  def goShoppingFuture(implicit
    ec: ExecutionContext
  ): Future[Unit] =
    Future(println("Going to the grocery store"))

  val goShopping: Task[Unit] = Task.fromFuture(implicit ec => goShoppingFuture)

还有许多其他构造函数可以从其他数据类型(例如java.util.concurrent.Future)创建ZIO效果,并提供第三方包以提供从Monix,Cats Effect和其他数据类型的转换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值