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
。通过这些我们可以轻易地写一个像这样的 repeat
组合器。
def repeat[E, A](n: Int)(io: IO[E, A]): IO[E, Unit] =
Ref.make(0).flatMap { iRef =>
def loop: IO[E, Unit] = iRef.get.flatMap { i =>
if (i < n)
io *> iRef.update(_ + 1) *> loop
else
IO.unit
}
loop
}
状态转换器
可变量的阴暗面在于它们能够轻易地被改变;它们可以像圣诞节装饰品一样添加在任何地方并改变状态。比如:
var idCounter = 0
def freshVar: String = {
idCounter += 1
s"var${idCounter}"
}
val v1 = freshVar
val v2 = freshVar
val v3 = freshVar
作为函数式程序员,我们相当了解如何对付它。我们可以 S =>(A, S)
类型的函数形式来捕获状态的改变。Ref
提供了这样的编码能力,S 用来表达值的类型,并通过 modify
引介状态改变函数。
Ref.make(0).flatMap { idCounter =>
def freshVar: UIO[String] =
idCounter.modify(cpt => (s"var${cpt + 1}", cpt + 1))
for {
v1 <- freshVar
v2 <- freshVar
v3 <- freshVar
} yield ()
}
构建更复杂的并发原语
Ref
的级别足够低,以至于可以用作其他并发数据类型的基础。
信号量是一种经典的用于控制对共享资源的访问的抽象数据类型。它的定义为一个三元组形式 S = (v, P, V),其中 v 是当前可用资源的单位数,P 和 V 分别是对 v 进行递减和递增的运算;P 只能在 v 是非负数的时候才能对它进行递减,否则必须等待直到非负为止。
现在,有了 Ref
s,我们可以很容易实现它!唯一的困难在于 P,我们必须在 v 为负数时,或在我们读取并试图改变它时,它恰好被(别的线程)改变了,这时让 P 失败并重试。 一个不成熟的实现看上去可以像下面这样:
sealed trait S {
def P: UIO[Unit]
def V: UIO[Unit]
}
object S {
def apply(v: Long): UIO[S] =
Ref.make(v).map { vref =>
new S {
def V = vref.update(_ + 1).unit
def P = (vref.get.flatMap { v =>
if (v < 0)
IO.fail(())
else
vref.modify(v0 => if (v0 == v) (true, v - 1) else (false, v)).flatMap {
case false => IO.fail(())
case true => IO.unit
}
} <> P).unit
}
}
}
现在让我们伴随前几天在市场上发现的这些鳄鱼皮靴子摇滚起来,在夜总会测试我们的信号灯吧,来吧!
import zio.duration.Duration
import zio.clock._
import zio.console._
import zio.random._
val party = for {
dancefloor <- S(10)
dancers <- ZIO.foreachPar(1 to 100) { i =>
dancefloor.P *> nextDouble.map(d => Duration.fromNanos((d * 1000000).round)).flatMap { d =>
putStrLn(s"${i} checking my boots") *> sleep(d) *> putStrLn(s"${i} dancing like it's 99")
} *> dancefloor.V
}
} yield ()
同时不用说,您应该看一下 ZIO 内建的 Semaphore
,它可以完成所有的这些甚至更多工作而不会浪费任何 CPU 周期。
多态的 Ref
s
Ref[A]
实际上是类型 ZRef[Nothing, Nothing, A, A]
的别名。ZRef
的类型签名如下:
trait ZRef[+EA, +EB, -A, +B]
ZRef
是对可变引用的多态的,纯函数的描述。它的基本操作包括 set
和 get
。set
接受一个类型为 A 的值并将引用设置为该新值,这个操作可能以 EA 错误类型失败。get
获取并返回当前的 B 类型的引用值,或者它可能以 EB 错误类型失败。
当 ZRef
的错误和值类型统一时,即 ZRef[E, E, A, A]
。ZRef
还支持如上所述的原子 modify
和 update
操作。
一个简单的用例是可以获得引用值的只读或只读视图:
for {
ref <- Ref.make(false)
readOnly = ref.readOnly
writeOnly = ref.writeOnly
_ <- writeOnly.set(true)
value <- readOnly.get
} yield value