前几天一个朋友向我抱怨,说Scala语言太晦涩了,他实在不明白为什么这么晦涩的语言反而被誉为具有更好的表达能力呢?我想就此解释一下。 我们要评价任何一个东西好或者坏,首先都必须先确定一个标准,评价一门编程语言也是一样,那应该如何确定一门语言的评价标准呢?它的语法算不算?通过语法来评价恐怕是许多人的第一反应,因为这个我们最熟悉,但是语法恰好不能作为评判标准的。除非是领域专有语言,否则不同的(同代)通用语言在逻辑表达上基本都大同小异,所谓“晦涩不晦涩”主要是因不同的人的习惯而言,就好比中文和英文,你觉得哪个更晦涩?熟悉中文的人认为中文的表达能力胜过英文,而熟悉英文的人则相反;同样,熟悉Word的人恐怕认为任何编程语言的表达能力都是垃圾,但是程序员则认为Word文档才是多余,所以“语法”这个东西,只要是同代编程语言,用于相同的目的,我们是不能通过主观来评价他们的表达力的,这难免会出现先入为主的偏差。 那么语法不是评判的标准,那么什么才是呢?应该是看它对业务的“持续追踪”能力。我们知道当我们使用一门语言来编程的时候,我们的最终目的是要尽可能完美地实现我们想表达的业务,这是最核心的问题,但是问题是我们在编写代码的过程中很难做到总是围绕这个核心来进行,因为我们不得不不断地跳出业务的正常框架,而去处理一些周边问题,比如处理异常问题,比如约束输入信息的格式,还有比如打印日志等等。所有的这些与正常业务有关联,但是又偏离业务主线的事情我们也需要通过代码来管理。编程语言究竟能够在多大程度上帮组我们将精力尽量聚焦在业务的主线表达上,这才是评价一门语言优劣最重要的标准。放在业务主线上的代码越连贯,业务的表达也就会越清晰,焦点问题也就越能得到有效梳理,这门语言它的表达力也就越强。 在泛函编程中有一个很重要的概念叫 effect,所谓“effect”也就是对核心问题的有效解答,effect 的一个特点就是尽可能地避免那些可能导致偏离主线的事情发生,尽可能地围绕核心业务逻辑,所谓核心业务逻辑就是业务正常状态下才应该发生的事情。当我们在用泛函语言编程的时候,我们要尽可能地在主线上串联 effect,相对于effect 而言其它的问题也就是 side effect(副作用),side effect 则要尽一切手段推送到程序的“边缘”去处理。 如果直接这么说很抽象的话,我们来做个类比。假设在现实生活中我们正在去银行开设一个账户,开设账户的过程需要经过,首先要出示证件,然后填写表格,柜台业务人员录入电脑,经主管审核确认……等等一系列操作。正常的开户流程肯定不应该聚焦在诸如证件丢失、表格缺页、电脑掉电、主管恰好上厕所……等等“异常”状态上,这就是 effect 和 side effect的区别,很显然我们不能忽略副作用的存在,正常的业务流程虽然不聚焦在这些问题上,但是对这些问题的发生是不可避免的,问题是当他们发生的时候都应该有相应的处理机制。我们不可能在正常的开户过程中要求主管先去上个厕所以免需要他确认的时候他不在的情况发生,但是在编程中这样的要求就的的确确不是开玩笑。所以理解了这个问题,我们回来比较Scala语言和Java的优劣就高下立判了。 传统编程语言的 effect 和 side effect 总是纠缠在一起的,甚至side fffect 的范围有时候比effect…
简介 ZIO 支持软件事务性内存 (STM),它是一种模块化的可组合的并发性数据结构。它允许我们在一个原子事务中组合并执行一组内存操作。 STM是一组并发任务之间的通信的抽象。STM 的主要优点是可组合性和模块化。我们可以编写可以与同样使用 STM 构建的任何其他抽象组合的并发抽象,同时不必暴露我们的抽象是如何确保安全的细节。而锁定机制则通常不是这样的。 Transactional 操作的想法并不新鲜,它们一直是分布式系统的基础,也是那些数据库之所以能够保证我们拥有 ACID 性质。STM是纯内存操作的。所有的操作都发生在内存中,与远程系统或数据库无关。与 ACID 属性的数据库概念非常相似,但缺少持久性,因为那对内存中的操作没有意义。 在事务性内存中,我们有关于 ACID 属性的几个方面: Atomicity(原子性)——在写操作中,我们需要原子更新,这意味着更新操作要么应该立即运行,要么根本不运行。Consistency(一致性)——在读操作中,我们希望程序状态具有一致的视图,以确保各部分都引用相同的状态,在获得状态时获得相同的值。Isolated(隔离性)——如果我们有多个同步更新,我们需要在隔离的事务中执行这些更新。每个事务不会影响其他并发事务。无论有多少纤程在运行多少数量的事务,都不必担心其它事务中发生的事情。 ZIO STM API 的灵感来自 Haskell 的 STM库,尽管…
秘钥和证书是有关安全最基本的概念之一,原本这个视频是要做两个。一个从概念和基本命令的角度讲解这两个要素如何工作;另一个介绍一些产业中经常用到的开源工具和服务,包括 k8s 中证书的应用。但是一直没有时间去做第二期,所以这一期将就看吧。 https://www.youtube.com/watch?v=mxfWnwEBRdI&t=3546s
早些年在IT资深专业协会做的一次关于 DevOps 的讲座。 https://www.youtube.com/watch?v=XY_43RqyZHs&t=15s https://www.youtube.com/watch?v=nbgiTCzWa7M&t=23s https://www.youtube.com/watch?v=qzSk_cCXIBU https://www.youtube.com/watch?v=BdzAqsk2hI4 https://www.youtube.com/watch?v=zIRk02QHOEU https://www.youtube.com/watch?v=0ABKQ3VQdL4 https://www.youtube.com/watch?v=XLWDNaV4q5o https://www.youtube.com/watch?v=CHHXtEcYUos
将一个实例赋予它的父类变量时,其赋值语言缺省具有 Covariant(协变) 语意: trait Animal case class Dog(name:String) extends Animal val animal:Animal = Dog(“Max”) 协变的语意是:当一个类型A(Animal)要接纳一个子类型B(Dog)的赋值时,尝试将B变为A的子类,系标记为+A。继承的父<-子方向为 A <- +A。 协变的语意和子类型多态的变态方向是相同的。正如上所示,Animal 是 Dog 的父类,因此将 Dog cast 成 Animal 是可以接受的。 容器类不具有类型继承关系,所以以下无法通过编译检查:…
网上有很多文章讲解 Monad,但是没有一篇让我感到满意,因为大多数文章不是从 Monad 的编程界面出发,就是从纯数学定义出发,这些都不能解决我们心中的根本问题,就是 “为什么需要 Monad?” 如果这个问题不解答,那么无论定义,还是使用,都只是照猫画虎而已。不知道为什么需要虎,那画虎就是纯粹炫技。 虽然本文试图解释为什么需要 Monad,但是不得不暂时离开 Monad 先谈一些别的。我们要先从为什么需要泛函编程讲起,其实这也是一个很难理解的问题。因为这里充满了禅宗式的崇拜,对于初学者而言,这种崇拜貌似有些虚无缥缈,所以不被接受,能打动他们的依然是炫技。这可能也反过来解释了为什么网上没有令人满意的答案的原因吧。 简而言之,泛函编程相对于面向对象的那些禅宗式的思想主要围绕两个方面展开。 安全。数学式的严谨。 我们首先要解答关于“安全”的疑问。这个问题其实在早先的一篇文章《一个有关泛型的错误考题》中曾提到。在那篇文章中我说,通用泛型是不安全的,因为它没有为运行时提供适用类型的边界指导,所以取而代之的是以 type class 为形式的特设泛型。详细内容不复述了,大家自己去参考。回到 type class,如果我们打开 Cats 的官方文档,我们会发现 Cats 的开篇第一句话就是对 type class…
这几天遇到了一个有关泛型的 Scala 试题,题目是这样的: class Comp[T] (val p1:T, val p2:T) { def getLarger = ??? def getSmaller = ??? } 要求实现这两个函数,我当场被震惊到了,这也太简单粗暴了。这道题目可以作为一个有关泛型的经典的反面教才来分析一下,看看我们能从中学到哪些东西。 我的第一反应是出题者应该是一个传统的 Javaer,他应该是受到 Java…
Here are a few guides for common patterns with ZIO: 使用模块和层: 如何借助ZIO环境构建大型的 ZIO 程序。Test effects: 如何使用 ZIO Test 无缝测试效果化的程序.Mock services: 如何使用模拟(mocks)来测试服务之间的交互。Handle errors: 如何处理 ZIO 中的错误(可声明的错误与无法预见的缺陷)。Access…
用 ZLayer 注入 ZIO 环境 ZIO 是围绕3个参数设计的,R, E, A。R 代表运行 effect 时的要求,这意味着我们需要满足这些要求才能使 effect 可运行。我们将探讨我们可以用 R 做些什么,因为 R 在 ZIO 中起着至关重要的作用。 有关 ZIO 环境的简单案例 让我们构建一个用户管理的简单程序,该程序可以检索用户(如果存在)和创建用户。我们需要一个 DBConnection…
interop-twitter 模块提供了将 Twitter Future 转换为 ZIO Task 的能力。 例子 import com.twitter.util.Future import zio.{ App, Task } import zio.console._ import zio.interop.twitter._ object Example extends App { def…
检出 interop-reactiveStreams 模块以获得对反应式流的互操作支持。 反应式流的 Producer 和 Subscriber ZIO 通过将 zio.stream.Stream 转换到 org.reactivestreams.Publisher,和将 zio.stream.Sink 转换到 org.reactivestreams.Subscriber 来集成 Reactive Streams。反之亦然。简单地引入 zio.interop.reactiveStreams._ 来让转换生效。 例子 首先,让我们导入一些内容。 import org.reactivestreams.example.unicast._ import zio._ import zio.interop.reactiveStreams._ import zio.stream._ val runtime = Runtime.default 我们使用以下发布者和订阅者作为示例: val publisher…
ZIO 实例 如果您是 Scalaz 7.2 的忠实用户,那么 interop-scala7x 模块为它的几种类型类提供了 ZIO 支持,请查看源代码以获取更多详细信息。 例子 import scalaz._, Scalaz._ import zio.interop.scalaz72._ type Database = IList[User] def findUser(id: UserId): ZIO[Database, UserError, User]…
签出 interop-monix 模块以获得与 Monix 的互操作支持。 转换 Task 互操作层提供对以下转换的支持: 将 Task[A] 转为 UIO[Task[A]]将 Task[A] 转为 Task[A] 要将 IO 转换为 Task,请使用以下方法: def toTask: UIO[eval.Task[A]] 要执行反方向的转换,请使用以下定义在 IO 伴随对象中的扩展方法: def fromTask[A](task: eval.Task[A])(implicit scheduler: Scheduler): Task[A]…
通过将以下内容添加到您的 build.sbt 中,使得在 Scala.js 项目中支持 ZIO: scalaJSUseMainModuleInitializer := true libraryDependencies += “dev.zio” %%% “zio” % “1.0.1” 例子 您的 main 函数可以像下面这样通过扩展 App 得到。这个例子使用 scala-js-dom 来访问 DOM;要运行该示例,您将需要将该库作为依赖项添加到 build.sbt中。 import…
ZIO 与外部 Java 代码具有完全的互操作性。让我向您展示它的工作原理,然后讲解第一个案例,明天您就可以在工作中使用纯函数式 Java 了。 From Java CompletionStage and back CompletionStage 是(Java 提供的)最便捷的用于模拟函数式异步效果的 API(例如 ZIO)的接口,因此我们从它开始。轻而易举地: def loggedStage[A](stage: => CompletionStage[A]): Task[A] = ZIO.fromCompletionStage(UIO { stage.thenApplyAsync {…
Scala Future ZIO 现在提供了与 Scala 的 Future 的基本互操作性,并且不需要额外的模块提供支持。 转换自 Future 可以使用 ZIO.fromFuture 将 Scala 的 Future 转换为 ZIO effect: def loggedFuture[A](future: ExecutionContext => Future[A]): UIO[Task[A]]…
interop-cats 模块提供了与 Cats Effect 生态系统之间的互操作性。 要使用此模块,请将以下内容添加到 build.sbt 中: libraryDependencies += “dev.zio” %% “zio-interop-cats” % “<version>” 大多数互操作功能包含在以下程序包中: import zio.interop.catz._ Cats Effect 实例 ZIO 通过提供 Cats Effect 类型类的实例与…
ZIO 提供与广泛的生态系统的其他部分进行互操作的能力,它们包含以下: Future — ZIO 提供内建的在 ZIO 数据类型(例如 ZIO 和 Fiber)和 Scala 并发数据类型(例如 Future)之间进行转换的能力。Java — ZIO 提供了内建的具在 ZIO 数据类型(例如 ZIO 和 Fiber)和 Java 并发数据类型(例如 CompletionStage, Future 和 CompletionHandler)之间进行转换的能力。JavaScript — ZIO 对 Scala.js…
TReentrantLock(可重入锁)允许效果性地、安全地并发访问某些可变状态,从而允多个纤程读取其中的状态(因为并发读取是安全的),但是只有一个纤程可以修改状态(以防止数据损坏)。此外,TReentrantLock 是使用 STM 实现的,读写操作都可以当作事务进行提交,因此可以将其用作纯 ZIO effect 解决方案的基石,并在内部允许以简单且可组合的方式锁定多个状态(感谢 STM)。 TReentrantLock 是可重入的读/写锁。可重入锁是一个纤程可以多次取得而不会对其自身造成阻塞的锁。在难以跟踪您是否已经获得锁的情况下,此功能很有用。如果锁是不可重入的,则在你获得该锁后再次去获得它时会被阻塞,从而实际上导致了死锁。 语义 该锁对读取方和写入方都提供了重新获得读取或写入锁的重入保证。在释放由写纤程持有的所有写锁之前,不允许读纤程进入。除非没有其他纤程持有写入锁,或者想要写入的纤程已经持有读取锁且没有其他纤程也持有读取锁,否则不允许写纤程进入。 此锁还允许从读锁升级到写锁(自动),以及从写锁降级到读锁(前提是您从读锁升级到写锁是自动的)。 创建一个可重入锁 import zio.stm._ val reentrantLock = TReentrantLock.make 获取一个读锁 import zio.stm._ val program…
TSemaphore 是具有事务语义的信号量,可用于控制对公共资源的访问。它拥有一定数量的许可证,并且可以获取或释放许可证。 创建一个 TSemaphore 创建一个具有 10 个许可证的 TSemaphore: import zio._ import zio.stm._ val tSemaphoreCreate: STM[Nothing, TSemaphore] = TSemaphore.make(10L) 获取一个许可证 一旦外部程序获得许可证,这会减少 TSemaphore 包含的剩余许可证数量。当用户想要访问受限共享资源时,就需要获取可: import zio._ import zio.stm._ val…
最近应“IT资深专业协会”的邀请做了一场有关设计原则和函数式编程的科普演讲,案例有些复杂,在这里将内容和代码简单重复一下,以便没有听明白的协友可以复习。 https://youtu.be/yy-iP5hz_uc 需求分析 案例是一个假设的电商系统的用户信息管理接口,需求大致为以下三点: 设计一个电商系统的用户信息管理子模块(ProfileService),满足新用户注册、登陆、和个人信息管理,包括头像、密码、住址等;用户信息管理模块必须能够支持来自订单系统(OrderService)的调用,提供必要的相关功能子集。用户信息管理模块必须能够支持来自客服系统(HelpDeskService)的调用,提供必要的相关功能子集。 先大致梳理一下订单系统到用户系统的 use case: 用户可以直接注册/登陆/或取回密码,也可以先下单(order),然后在提交订单的时候再注册/登陆,并且可以添加/修改寄送地址,因此 OrderService 需要能够访问用户的 Profile。基于这样的需求,我们得到以上 use case,并根据 use case 的功能,得到以下 ProfileService 及其接口的类图设计: 接口隔离 我们得到了一个初步的版本,但是这个版本是有缺陷的。很显然它没有考虑接口的应用。 对于订单模块 OrderService 和负责登陆认证的 AuthService…
TSet[A] 是一个可以参与 STM 中事务可变集合。 创建一个 TSet 创建一个空的 TSet: import zio._ import zio.stm._ val emptyTSet: STM[Nothing, TSet[Int]] = TSet.empty[Int] 或创建具有指定值的 TSet: import zio._ import zio.stm._ val specifiedValuesTSet: STM[Nothing,…
TRef[A] 是可以参与 STM 事务的对不可变值的可变引用。其可变引用可以在事务内检索和设置,并被强制保证原子性,一致性并与其他事务的隔离。 TRef 在 STM 内存中发生改变时,使用了低级机制来创建事务。 创建一个 TRef 在事务内部创建TRef: import zio._ import zio.stm._ val createTRef: STM[Nothing, TRef[Int]] = TRef.make(10) 或者在事务内创建一个TRef,然后立即提交该事务,这使您可以存储并传递一个值引用。 import zio._ import zio.stm._ val…
TQueue[A] 是一个可以参与 STM 事务的可变队列。 创建一个 TQueue 创建一个具有指定容量的空的有界 TQueue: import zio._ import zio.stm._ val tQueueBounded: STM[Nothing, TQueue[Int]] = TQueue.bounded[Int](5) 创建一个空的无容量限制的 TQueue: import zio._ import zio.stm._ val tQueueUnbounded:…
TPromise 是一个可以设置一次,并且可以参与 STM 事务的可变参考。 创建一个 TPromise 创建一个 TPromise: import zio._ import zio.stm._ val tPromise: STM[Nothing, TPromise[String, Int]] = TPromise.make[String, Int] 结束一个 TPromise 成功完成 TPromise: import zio._ import…
TPriorityQueue[A] 是一个可以参与 STM 事务的可变队列。一个 TPriorityQueue 中包含了类型为 A 的带有顺序定义的值。与 TQueue 不同,take 返回的是最高优先级(指定顺序中的第一个)值,而不是队列中的第一个值。当从队列中取出时,不保证共享相同优先级的元素的顺序。 创建一个 TPriorityQueue 您可以使用 empty 函数创建一个空的 TPriorityQueue: import zio._ import zio.stm._ val minQueue: STM[Nothing, TPriorityQueue[Int]] = TPriorityQueue.empty 请注意,TPriorityQueue 的创建使用了隐式…
TMap[A] 是一种可以参与 STM 事务的可变映射。 创建一个 TMap 创建一个空的 TMap: import zio._ import zio.stm._ val emptyTMap: STM[Nothing, TMap[String, Int]] = TMap.empty[String, Int] 或创建具有指定值的 TMap: import zio._ import zio.stm._ val…
TArray 是可以参与 STM 事务的可变引用的数组。 创建一个 TArray 创建一个空的 TArray: import zio._ import zio.stm._ val emptyTArray: STM[Nothing, TArray[Int]] = TArray.empty[Int] 或创建具有指定值的 TArray: import zio._ import zio.stm._ val specifiedValuesTArray: STM[Nothing,…
特质 Has[A] 与 ZIO 环境一起使用,以表示一个 effect 对 A 类型服务的依赖性。 例如,RIO[Has[Console.Service], Unit] 表示一个需要 Console.Service 服务的 effect。
ZLayer[A, E, B] 描述应用程序的一层:应用程序中的每个层都需要一些服务(作为输入)并产生出一些服务(作为输出)。Layers 可以被视基于给定他们的依赖关系(其他服务)而产生这些服务绑定所需要的清单。 层的构造可以使资源的使用效果化,并且使资源被安全地获取,使用完后安全地释放。 默认情况下,层(Layer)是共享的,这意味着如果同一图层使用两次,则该图层将仅分配一次。由于层具有出色的可组合性,因此它们是 ZIO 中创建依赖于其他服务的服务的惯用方式。 最简单的 ZLayer 应用 import zio._ object Example extends zio.App { def run(args: List[String]): URIO[ZEnv, ExitCode] = zio.provideLayer(nameLayer).as(ExitCode.success)…
Chunk[A] 代表一大块类型为 A 的值。块的设计块通常由数组支持,但向下层元素公开纯函数式的,安全的接口,并且它们对使用数组代价高昂的操作(例如重复级联)采用懒模式。 新建一个块(chunk) Chunk(1,2,3) // res0: Chunk[Int] = Chunk.IntArray(1, 2, 3) 块连接(Concatenating): ++ 操作返回当前块与指定块的串联块。例如: Chunk(1,2,3) ++ Chunk(4,5,6) // res1: Chunk[Int] = Chunk.Concat(1, 2, 3, 4,…
Stream[E, A] 表示一个可以产生 A 类型输出值,或可能以 E 类型为失败值的,效果化的流。 新建 Stream import zio.stream._ val stream: Stream[Nothing, Int] = Stream(1,2,3) 或产生自 Iterable: import zio.stream._ val streamFromIterable: Stream[Nothing, Int] =…
ZSink[R, E, A, B] 用于消费从流中产生的元素。您可以将此接收器视为消费可变数量的 A 元素(可能为 0、1或很多!)的函数,可能因 E 类型错误而失败,或最终产生 B 类型的值。 ZSink 被作为参数传递给 ZStream#run: import zio._ import zio.stream._ val stream = ZStream.fromIterable(1 to 1000) val…
在 OO 中如果我们要实现某种“行为”,一般来讲我们会采用在 interface 或抽象基类中定义方法,然后在子类中实现方法的做法,这也是 OO 所倡导的“继承”和“重写”。假设我们现在要写一个“超级玛丽”游戏,在这个游戏中我们当然要实现一个 Mario,并且这个 Mario 至少需要会 move 和 Jump,也就是说我们肯定需要这样两个方法。并且考虑到游戏中肯定不止只有 Mario 会跑会跳,很多怪物也都会,所以我们需要设计一个叫 Npc 的基类,并且在这里定义一些虚函数,于是我们得到这样一个实现: public interface Npc { void jump(); …
信号量 Semaphore 数据类型,它允许通过 withPermit 方法在纤程之间进行同步,该方法可以安全地获取和释放许可证。信号量是基于 Ref[A] 数据类型的。 操作 例如,异步任务可以通过获取和释放具有给定数量许可的信号量来完成彼此的同步。当信号量中的许可值不足,获取操作无法执行时,该任务将在纤程队列中被置于挂起状态,直到有足够的许可值时被唤醒: import java.util.concurrent.TimeUnit import zio._ import zio.console._ import zio.duration.Duration val task = for { _ <- putStrLn(“start”)…
import zio._ Schedules 允许你定义和编写灵活的重复执行调度器,这些事件可以是重复的计算,或在出现错误时重试操作。Schedules 被用于以下场景: 重复IO#repeat —— 重复执行 effect,直到调度计划结束。IO#repeatOrElse —— 重复执行 effect,直到调度计划完成,如果得到错误,则返回另一个 effect 的结果。IO#repeatOrElse0 —— 重复执行 effect,直到调度计划完成,如果得到错误,则返回另一个具有更多(定制)能力的 effect 的结果。RetriesIO#retry —— 重试一个 effect 直到成功。IO#retryOrElse —— 运行一个 effect,如果失败则尝试另一个 effect,不断重试两者直到成功。IO#retryOrElse0 —— 运行一个 effect,如果失败则尝试另一个具有更多(定制)能力的 effect,不断重试两者直到成功。…
Ref[A] 是对 A 类型的可变引用值的建模。它有两个基本操作:set,向 Ref 中填入新值;get,从中获得当前的值。Ref 上所有的操作都是原子且线程安全的,这为同步并发程序提供了可靠的基础。 import zio._ for { ref <- Ref.make(100) v1 <- ref.get v2 <- ref.set(v1 – 50) } yield v2 更新一个 Ref 使用 Ref 最简单的手段是使用 update 或它更强大的好兄弟 modify。通过这些我们可以轻易地写一个像这样的…
Queue 是一个基于 ZIO 的轻量级的,驻扎在内存的队列,具有可组合性,并且支持透明的背压。它是完全异步(无锁或无阻塞)的,纯函数且类型安全的。 Queue[A] 包含的值的类型为 A,并且具有两个基本操作:offer,将 A 放入队列中,和 take, 删除并返回队列中最旧的值。 import zio._ val res: UIO[Int] = for { queue <- Queue.bounded[Int](100) _ <- queue.offer(1) v1…
Promise[E, A] 是只能被设置一次的 IO[E, A] 类型的变量。 Promise 用于构建更高级别的并发原语,通常用于需要多个纤程相互协调传递值的情况。 建立 可以使用 Promise.make[E, A] 来创建 Promise,它返回 UIO[Promise[E, A]]。这是对创建 Promise 的描述,而不是实际的 Promise。不能在 IO 外部创建Promise,因为创建它们涉及分配可变内存,这是一种 effect,必须安全地在 IO 中捕获。 运算 完成 您可以通过几种不同的方式完成 Promise[E, A]:…
Managed 是一个封装了资源的 acquire 和 release 的数据结构。 Managed[E, A] 表示一个类型为 A 的托管资源,它可以通过 use 方法被使用。资源将在使用之前自动获取资源,并在使用之后自动释放资源。 如果资源无法在 use 范围内生效,这意味着您可能在获得资源后,在 use 中将其浪费掉,然后在资源消耗完后再次使用它,根据资源提供的功能类型它可能已经不再有效,并且可能会因检查错误而失败。 import zio._ def doSomething(queue: Queue[Int]): UIO[Unit] = IO.unit val…