IO[E, A]
类型的值,是一个可能导致 E
类型的失效,或永远运行,或产生 A
类型成功值的 effect。
IO 的值是不可变的,并且所有的 IO 函数都会产生新的 IO 值,使得 IO 像其它普通的 Scala 不变数据结构一样可以被推理和使用。
IO
值实际上啥也不做;它们只是一个描述交互效果的模型
的值。
IO 可以被 ZIO 运行时系统解释成与外部世界的交互效果。理想情况下,这个过程在应用程序的 main
函数中一次性发生的。 App
类自动提供了此功能。
纯值
您可以通过 IO.succeed
将一个纯值装载入 IO
import zio.{ UIO, IO }
val value: UIO[String] = IO.succeed("Hello World")
绝对不要使用任何构造函数将不纯代码导入 IO。这样做的结果是不确定的。
不会失败的 IO
UIO[A]
类型的 IO 值(它的错误类型为 Nothing
)被认为是不会失败的,因为Nothing
类型表示不存在的,即,不能有 Nothing
类型的实际值。此类型的值可能会产生 A
类型的成功结果,但永远不会导致 E
类型的失败。
不产生有效值的 IO
IO[E, Nothing]
类型的 IO
值(其中值类型为 Nothing
)被认为是无有效值的,因为 Nothing
类型表示不存在的,即不能有 Nothing
类型的实际值。此类型的值可能会得到 E
类型的失败,但永远不会产生成功的结果值。
不纯的代码
您可以使用 IO 的 effectTotal
方法将同步效果的代码导入为纯函数程序:
val effectTotalTask: Task[Long] = IO.effectTotal(System.nanoTime())
它的执行结果有可能是任何 Throwable
类型的失败。
如果(这个结果的)范围太广,可以使用 ZIO 的 refineOrDie
方法仅保留对某些类型的异常的关注,而其他任何类型的异常则直接导致“死亡”:
def readFile(name: String): IO[IOException, Array[Byte]] =
IO.effect(FileUtils.readFileToByteArray(new File(name))).refineToOrDie[IOException]
您可以使用 IO 的 effectAsync
方法将异步效果的代码导入为纯函数程序:
def makeRequest(req: Request): IO[HttpException, Response] =
IO.effectAsync[HttpException, Response](k => Http.req(req, k))
在此示例中,假设 Http.req
方法在得到异步执行的结果后将调用指定的回调函数。
映射
您可以通过调用 map
方法时给予函数 A => B
来将 IO[E, A]
更改为 IO[E, B]
。这可以将前一操作产生的值转换为另一个值。
import zio.{ UIO, IO }
val mappedValue: UIO[Int] = IO.succeed(21).map(_ * 2)
您可以在调用 mapError
方法时使用函数 E => E2
,将 IO[E, A]
转换为 IO[E2, A]
:
val mappedError: IO[Exception, String] = IO.fail("No no!").mapError(msg => new Exception(msg))
链式调用
您可以使用 flatMap
方法依次执行两个操作。第二动作的执行取决于第一动作产生的值。
val chainedActionsValue: UIO[List[Int]] = IO.succeed(List(1, 2, 3)).flatMap { list =>
IO.succeed(list.map(_ + 1))
}
您可以使用 Scala 的 for comprehension
语法使这种类型的代码更紧凑:
val chainedActionsValueWithForComprehension: UIO[List[Int]] = for {
list <- IO.succeed(List(1, 2, 3))
added <- IO.succeed(list.map(_ + 1))
} yield added
Brackets
bracket
是内置原语,可让您安全地获取和释放资源。
Brackets 被用在类似 try/catch/finally
的场景,但是 brackets 可以被用于同步和异步,可以和纤程中断无缝配合。它基于不同的错误模型构建,以确保不会丢失任何错误。
Brackets 由一个获取方法,一个使用方法(用于使用获取的资源),和一个释放方法组成。
释放
动作由运行时来保证执行,哪怕使用
中抛出了异常或执行的纤程被中断。
import zio.{ UIO, IO }
val groupedFileData: IO[IOException, Unit] = openFile("data.json").bracket(closeFile(_)) { file =>
for {
data <- decodeData(file)
grouped <- groupData(data)
} yield grouped
}
Brackets 支持组合语义,因此,如果将一个 bracket 嵌套在另一个 bracket 内,如果外部 bracket 获取了资源,那么即使内部 bracket 的释放失败,外部 bracket 的释放动作也依然会得到执行。
有一个名为 ensuring
的方法提供了另一个类似 finally
的功能:
var i: Int = 0
val action: Task[String] = Task.effectTotal(i += 1) *> Task.fail(new Throwable("Boom!"))
val cleanupAction: UIO[Unit] = UIO.effectTotal(i -= 1)
val composite = action.ensuring(cleanupAction)
一个完整的使用 brackets 的例子
import zio.{ ExitCode, Task, UIO }
import java.io.{ File, FileInputStream }
import java.nio.charset.StandardCharsets
object Main extends App {
// run my bracket
def run(args: List[String]) =
mybracket.orDie.as(ExitCode.success)
def closeStream(is: FileInputStream) =
UIO(is.close())
// helper method to work around in Java 8
def readAll(fis: FileInputStream, len: Long): Array[Byte] = {
val content: Array[Byte] = Array.ofDim(len.toInt)
fis.read(content)
content
}
def convertBytes(is: FileInputStream, len: Long) =
Task.effect(println(new String(readAll(is, len), StandardCharsets.UTF_8))) // Java 8
//Task.effect(println(new String(is.readAllBytes(), StandardCharsets.UTF_8))) // Java 11+
// mybracket is just a value. Won't execute anything here until interpreted
val mybracket: Task[Unit] = for {
file <- Task(new File("/tmp/hello"))
len = file.length
string <- Task(new FileInputStream(file)).bracket(closeStream)(convertBytes(_, len))
} yield string
}
王继
Posted on August 31, 2020 at 3:20 pm“不存的代码” 小节中有关 effectTotal 的用法有“它的执行结果有可能是任何 Throwable 类型的失败。”的表述容易引起歧义,源代码中关于这个函数的注解是:“Imports a total synchronous effect into a pure `ZIO` value. 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]].”。