创建 effect

本章节将探讨从直接量、常见的 Scala 类型,和同步、异步 side effect 中创建 ZIO effect 的一些常用方法。

必定成功的值

使用 ZIO.succeed 方法,你可以为一个特定值建立一个成功的 effect。:

val s1 = ZIO.succeed(42)

也可以在 ZIO 的别名中使用这些方法:

val s2: Task[Int] = Task.succeed(42)

succeed 方法接收一个 by-name 参数以确保在构造该值时产生的任何 side-effect 都可以被 ZIO 的运行时适当地管理。但是 succeed 倾向于被用在没有任何 side-effect 的求值。如果你确定这个值会产生 side-effect,请考虑使用 ZIO.effectTotal 来声明:

val now = ZIO.effectTotal(System.currentTimeMillis())

这个成功效果的构造器 ZIO.effectTotal 只在必要的时候才会产生出值。

必定失败的值

使用 ZIO.fail 来为失败效果建模。

val f1 = ZIO.fail("Uh oh!")

ZIO 数据类型不限定错误的类型,您可以使用适合您的应用程序的字符串,异常或自定义数据类型作为错误类型。

许多程序使用扩展自 Throwable 或 Exception 的类来为错误建模:

val f2 = Task.fail(new Exception("Uh oh!"))

需要注意的是,不同于其它效果伴随对象,UIO 的伴随对象没有 UIO.fail,因为由 UIO 表达的 effect 不会失败。

取自 Scala 类型

很多来自 Scala 标准库的数据类型都可以被转换成 ZIO effect。

Option

可以通过  ZIO.fromOptionOption 转换成 ZIO effect:

val zoption: IO[Option[Nothing], Int] = ZIO.fromOption(Some(2))

它返回的错误效果类型是 Option[Nothing],它没有提供有关为何不存在该值的信息。你可以通过 ZIO#mapError 将 Option[Nothing] 映射到特定的错误类型:

val zoption2: IO[String, Int] = zoption.mapError(_ => "It wasn't there!")

你还可以轻松地组合不同的运算并保持“可选性”的结果 (与 OptionT 类似):

val maybeId: IO[Option[Nothing], String] = ZIO.fromOption(Some("abc123"))
def getUser(userId: String): IO[Throwable, Option[User]] = ???
def getTeam(teamId: String): IO[Throwable, Team] = ???

val result: IO[Throwable, Option[(User, Team)]] = (for {
  id   <- maybeId
  user <- getUser(id).some
  team <- getTeam(user.teamId).asSomeError 
} yield (user, team)).optional 

Either

通过 ZIO.fromEither 可以将 Either 转换成 ZIO effect:

val zeither = ZIO.fromEither(Right("Success!"))

得到的 effect 中的错误类型将是任意的左值(Left)类型,而成功类型将是任意的右值(Right)类型。

Try

通过 ZIO.fromTry 可以将 Try 转换成 ZIO effect:

import scala.util.Try

val ztry = ZIO.fromTry(Try(42 / 0))

得到的 effect 中的错误类型总是 Throwable,因为 Try 只会以 Throwable 作为失败类型。

Function

通过 ZIO.fromFunction 可以将函数 A => B 转换成 ZIO effect

val zfun: URIO[Int, Int] =
  ZIO.fromFunction((i: Int) => i * i)

该 effect 的环境类型是 A (等于函数的输入类型),因为为了让该 effect 的到执行,我们必须提供该类型的输入值。

Future

通过 ZIO.fromFuture 可以将一个 Future 转换成 ZIO effect:

import scala.concurrent.Future

lazy val future = Future.successful("Hello!")

val zfuture: Task[String] =
  ZIO.fromFuture { implicit ec =>
    future.map(_ => "Goodbye!")
  }

这个函数将一个  ExecutionContext 传递 fromFuture 函数,它将允许 ZIO 管理 Future 的运行 (当然,你也可以忽略该 ExecutionContext).

返回效果的错误类型总是 Throwable,因为 Future 只会以 Throwable 作为失败类型。

取自副作用(Side-Effects)

ZIO可以将同步和异步 side-effect 都转换为ZIO effect(纯值)。

以下这些函数可用于包装过程代码,从而使您可以将ZIO的所有功能与遗留的 Scala 和 Java 代码以及第三方库无缝地结合使用。

同步 side-effect

ZIO.effect 可以用于将有 side-effect 的同步代码转换成 ZIO:

import scala.io.StdIn

val getStrLn: Task[String] =
  ZIO.effect(StdIn.readLine())

结果的错误类型总是 Throwable,因为 side-effect 可能会引发任何类型的 Throwable 类型的异常。

如果给出的 side-effect 代码不会抛出任何异常,那么它可以用 ZIO.effectTotal 来转换。

def putStrLn(line: String): UIO[Unit] =
  ZIO.effectTotal(println(line))

当你使用 ZIO.effectTotal 的时候要特别小心 —— 如果你不能完全确定没有 side-effect,那么请使用 ZIO.effect 。

如果您希望细化效果的错误类型(将其他错误视为致命错误),那么可以使用 ZIO#refineToOrDie

import java.io.IOException

val getStrLn2: IO[IOException, String] =
  ZIO.effect(StdIn.readLine()).refineToOrDie[IOException]

异步 side-effect

ZIO.effectAsync 可以将一个基于回调 API 的异步 side-effect 转化成 ZIO effect:

object legacy {
  def login(
    onSuccess: User => Unit,
    onFailure: AuthError => Unit): Unit = ???
}

val login: IO[AuthError, User] =
  IO.effectAsync[AuthError, User] { callback =>
    legacy.login(
      user => callback(IO.succeed(user)),
      err  => callback(IO.fail(err))
    )
  }

与基于回调的API相比,异步 ZIO effect 更易于使用,并且它将受益于 ZIO 提供的功能,例如中断、资源安全和出色的错误处理。

阻塞的同步 Side-Effects

有一些 side-effect 代码使用阻塞 IO 或以某总方式使线程进入等待状态,如果你不小心加以管理,这些 side-effect 代码可能会耗尽应用程序主线程池中的线程,从而导致资源匮乏。

ZIO 提供了 zio.blocking 包,可以用于将阻塞 side-effects 安全地转换到 ZIO effects.

可以使用 effectBlocking 方法将阻塞的 side-effect 直接转换为阻塞的 ZIO effect:

import zio.blocking._

val sleeping =
  effectBlocking(Thread.sleep(Long.MaxValue))

产生的 effect 将在专门为阻塞效果而设计的单独线程池上执行。

可以使用 effectBlockingInterrupt 调用 Thread.interrupt 来中断阻塞副作用。

import zio.blocking._

def longRunning: Unit = {
    println("start")
    Thread.sleep(2000)
    println("end")
}
effectBlockingInterrupt(longRunning).timeout(Duration.Zero)

可以使用  effectBlockingCancelable  来将某些只能通过调用 cancellation 来终止的阻塞性 side-effect 转换成 effect。

import java.net.ServerSocket
import zio.UIO

def accept(l: ServerSocket) =
  effectBlockingCancelable(l.accept())(UIO.effectTotal(l.close()))

如果副作用已经转换为ZIO effect,则可以使用 blocking 方法来代替  effectBlocking,以确保在阻塞线程池上执行该效果:

import scala.io.{ Codec, Source }

def download(url: String) =
  Task.effect {
    Source.fromURL(url)(Codec.UTF8).mkString
  }

def safeDownload(url: String) =
  blocking(download(url))

下一步

如果您接受根据值,Scala 数据类型和副作用创建 effect,则下一步是学习 effect的基本操作

One thought on “创建 effect
  1. 王继
    Posted on August 20, 2020 at 3:58 am Reply

    “如果你确定这个值会产生 side-effect,请考虑使用 ZIO.effectTotal 来声明” 的原文是:“ If you know that your value does have side effects consider using ZIO.effectTotal for clarity.” —— 这句话的上下文仅限于调用必定成功的情况,该函数的注释明确说明,如果调用会产生异常则不能使用该函数:“The effect must not throw any exceptions. If you wonder if the effect throws exceptions, then do not use this method, use [[Task.effect]], [[IO.effect]], or [[ZIO.effect]].”

Leave a Reply
Your email address will not be published.
*
*

BACK TO TOP