IO

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
}
One thought on “IO
  1. 王继
    Posted on August 31, 2020 at 3:20 pm Reply

    “不存的代码” 小节中有关 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]].”。

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

BACK TO TOP