2.4 zio入门——ZIO类型参数

2.4 ZIO类型参数

我们之前说过,类型 ZIO [R,E,A]的值是一种 functional effect,需要环境R,并且可能因E失败或成功返回A。
现在,我们也了解了ZIO effect 成为并发工作流程的蓝图意味着什么,以及如何组合效果,下面让我们详细讨论每个ZIO类型参数:

  • R是 effect 要执行效果所需的环境。一般指的是 effect 需要的一些依赖关系,例如访问数据库或日志记录服务,或者效果可能不需要任何环境,在这种情况下,类型参数将为Any
  • E是效果可能失效的值的类型。这可能是Throwable或Exception,但也可能是特定领域的错误类型,或者效果可能根本无法失败,在这种情况下,type参数将为Nothing
  • A是效果可以成功的值的类型。可以将其视为效果的返回值或输出。
    理解这些类型参数的一种有用方法是将ZIO effect 想象为函数R => Either [E,A]。这并不是ZIO的实现方式(此定义不允许我们编写并发,异步或资源安全的代码
    算子),但这是一个有用的思维模型。

以下代码段定义了这种 ZIO effect 的 toy model:

  final case class ZIO[-R, +E, +A](run: R => Either[E, A])

从该定义可以看出,R参数是输入(为了执行效果,必须提供R类型的值),而E和A参数是输出。 输入被声明为逆变的,而输出被声明为协变的。有关 variance 的更详细讨论,请参见附录。 否则,只要知道Scala variance注释会改善类型推断,这就是ZIO使用它们的原因。

让我们看看如何使用这种思维模型来实现一些基本的构造函数和运算符:

final case class ZIO[-R, +E, +A](run: R => Either[E, A]) {
  self =>
  def map[B](f: A => B): ZIO[R, E, B] = ZIO(r => self.run(r).map(f))
  def flatMap[R1 <: R, E1 >: E, B](f: A => ZIO[R1, E1, B]): ZIO[R1, E1, B] = ZIO(
    r => self.run(r).fold(ZIO.fail(_), f).run(r)
  )
}

object ZIO {
  def effect[A](a: => A): ZIO[Any, Throwable, A] = ZIO(
    _ =>
      try Right(a)
      catch {
        case t: Throwable => Left(t)
      }
  )
  def fail[E](e: => E): ZIO[Any, E, Nothing] = ZIO(_ => Left(e))
}

ZIO.effect方法将代码块包装在一个effect中,将异常转换为Left值,并将成功转换为Right值。请注意,ZIO.effect的参数是按名称命名的(使用 :=> 符号),这可以防止对代码进行急切的计算,从而允许ZIO创建描述执行的值。
我们还实现了前面讨论的flatMap运算符,该运算符使我们可以顺序组合效果。 flatMap的实现如下:

  1. 首先使用环境R1运行原始效果,以生成Either [E,A]
  2. 如果原始效果因Left(e)失败,它将立即以Left(e)返回此失败。
  3. 如果原始效果以Right(a)成功,它将在该a上调用f以产生新效果。然后,它将在所需的环境R1中运行该新效果。
    如上所述,ZIO effect 实际上并不是这样实现的,但是执行一个效果,获取其结果然后将其传递给下一个效果的基本思想是一个准确的心理模型,它将帮助你在使用zio的过程中思考。
    我们将很快学习更多的方法来处理成功的ZIO值,但是现在让我们着重研究错误和环境类型,以建立其对他们的直觉,因为他们可能不那么直观。

2.4.1 错误类型

错误类型表示效果潜在的失败原因。 错误类型很有用,因为它允许我们使用类似 flatMap 这样的运算符来处理成功的返回值,同时将错误处理推迟到更高级别。 这使我们能够专注于程序的“快乐之路”并在正确的地方处理错误。
例如,假设我们要编写一个简单的程序,该程序从用户那里获得两个数字并将它们相乘:

import zio._
lazy val readInt: ZIO[Any, NumberFormatException, Int] =
  ???
lazy val readAndSumTwoInts: ZIO[Any, NumberFormatException, Int] = for {
  x <- readInt
  y <- readInt
} yield x * y

请注意,readInt的返回类型为ZIO [Any,NumberFormatException,Int],指示它不需要任何环境,并且如果成功的话,可以返回整数,如果失败,则抛出NumberFormatException。
错误类型的第一个好处是,我们仅凭其签名就知道该函数将如何失败。我们对readInt的实现一无所知,但仅查看类型签名,我们就知道它可能因NumberFormatException而失败,也不会因任何其他错误而失败。这非常强大,因为我们确切知道潜在的错误类型,而且我们永远不必借助“防御性编程”来处理未知错误。第二个好处是,假设效果成功,我们就可以对效果结果进行操作,将错误处理推迟到以后。
如果两个readInt调用均失败,并出现NumberFormatException,则readAndSumTwoInts也将失败,并出现异常,并中止求和。此簿记将自动为我们处理。我们可以直接将x和y相乘,而不必显式地处理失败的可能性。这将错误处理逻辑推迟给调用方,后者可以重试,报告或推迟更高级别的处理。
能够了解效果可能如何失败并将错误推迟到应用程序的更高级别很有用,但是在某些时候,我们需要能够处理一些或所有错误。

要处理我们的ZIO玩具模型中的错误,我们实现一个名为foldM的运算符,如果原始效果失败,它将使我们执行一个效果,如果成功,则将执行另一个操作:

  final case class ZIO [-R, +E, +A](run : R => Either [E, A]) {
    self =>
    def foldM [R1 <: R, E1, B](
                                failure : E => ZIO [R1, E1, B],
                                success : A => ZIO [R1, E1, B]
                              ) : ZIO [R1, E1, B] = ZIO(r => self.run(r).fold(failure, success).run(r))
  }

实际上,该实现与我们上面介绍的flatMap非常相似。 我们只是使用failure函数在出现错误的情况下返回新的效果,然后运行该效果。
错误类型最有用的功能之一是能够使指定效果完全不会失败,这可能是因为已经捕获并处理了它的错误。
在ZIO中,我们通过指定Nothing作为错误类型来做到这一点。 由于没有Nothing类型的值,因此我们知道,如果我们有Either [Nothing,A],则它必须是Right。 我们可以使用它来实现错误处理运算符,这些运算符可以让我们静态地证明效果不会因为我们已经处理了所有错误而失败。

2.4.2 环境类型

现在,我们对错误类型有了一些印象,接下来让我们关注环境类型。
我们可以通过Any来为任何不需要环境的effect进行建模。毕竟,如果effect需要类型为Any的值,则可以使用 ()(单位值),42或任何其他值来运行效果。 因此,可以使用任何类型的值运行的 effect 实际上是不需要任何特定类型环境的 effect。
使用环境的两个基本操作是访问环境(例如,获得对数据库的访问以执行某些操作)和提供环境(在测试的时候提供测试环境,在生产的时候提供生产环境)。

我们可以在ZIO的玩具模型中实现这一点,如以下代码片段所示:

  final case class ZIO[-R, +E, +A](run: R => Either[E, A]) {
    self =>
    def provide(r: R): ZIO[Any, E, A] = ZIO(_ => self.run(r))
  }
  
  object ZIO {
    def environment[R]: ZIO[R, Nothing, R] =
      ZIO(r => Right(r))
  }

如您所见,provider运算符会返回不需要任何环境的新效果。 环境构造函数使用所需的环境类型创建一个新效果,并将其作为成功值传递给环境。 这使我们可以访问环境并使用其他运算符(例如map和flatMap)进行处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值