理解Scala中的IO Monad

6

我正在学习scalaz,现在我试图理解IO单子的意义。我阅读了关于IO单子的这篇文章,并试图运行最简单的示例:

val io = println("test").pure[IO]
println("before")
io.unsafePerformIO()

是的,它按预期工作。它会打印

before
test

但是我无法理解IO单子的要点。有什么诀窍吗?除了我参考的文章中指定的“维护替换”之外。

现在看来,替换似乎不太有用。你能解释一下吗?

从我所想象的来看,假设我有一些特性:

trait Reader{
    def read(): List[Int]
}

trait Writer[T]{
    def write(t: T): Unit
}

我有一个读取器,可以读取单子值(在我的情况下是 List)。然后我需要将容器中的所有值写入到其他地方,并对它们执行一些转换。在这种情况下,IO 单子是否有用?

1个回答

2

IO无处不在,它使程序变得实用,因为我们不能整天计算纯表达式。

IO单子试图解决使IO操作“不纯”的问题,例如从不纯源(如网络)获取数据。

就IO本身而言,它并非引用透明的。想一想返回Unit的方法,比如println。让我们尝试将println替换为仅使用Unit(或()),我们将无法获得相同的值,因为println具有向控制台打印的效果。

以您的示例为例,想象一下:

def write(t: T): Unit

如果我们用()替换write会发生什么?那么,会产生向外部源写入的效果。但是,如果我们使用IO[Unit],就不会破坏替换。
要实际查看您的问题,我们需要将List单子与IO单子结合起来,因为我们知道单子不能组合。我们需要借助Monad转换器进行一些诡计:
import cats._
import cats.data.Nested
import cats.effect.IO
import cats.implicits._

val nested = Nested(IO.pure(reader.read()))
val res: Nested[IO, List, IO[Unit]] = 
   Functor[Nested[IO, List, ?]].map(nested)(i => writer.write(i))

val ioResult = for {
  listOfIO <- res.value
  flattened <- Applicative[IO].sequence(listOfIO)
} yield flattened

ioResult.unsafeRunSync()

这并不是很漂亮,可能没有直接调用返回Unit的有副作用操作那么简单,但我相信在组合单子方面有更好的方法,而我在这里创建了一个示例来说明。


谢谢,但我有一些关于转换IO[List[T]]的问题。Scalaz是否有适用于IO单子的转换器?使用IoT[T]将非常方便。 - St.Antario
2
如果你有 IO[List[T]],那么你需要一个 ListT 转换器,而 scalaz 已经提供了这个功能。 - Yuval Itzchakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接