本章节将探讨从直接量、常见的 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.fromOption
将 Option
转换成 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的基本操作。
王继
Posted on August 20, 2020 at 3:58 am“如果你确定这个值会产生 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]].”