我最近观看了一个介绍如何使用IO单子的视频,这个演讲是用scala语言进行的。我在想为什么需要从函数中返回IO[A]类型的值。封装在IO对象中的lambda表达式是有副作用的,而且在更高层次上必须被执行以观察到它们的变化,也就是说某些事情发生了。你难道不是在把问题推到树的其它位置吗?
我唯一能看出来的好处就是它允许懒惰求值,也就是说如果你不调用unsafePerformIO操作,就不会产生副作用。此外,我认为程序的其他部分可以使用/共享代码,并决定何时希望副作用发生。
我想知道这是否就是全部?它是否具有可测试性的优势?我认为并没有,因为您必须观察到效果,这有点否定了它的可测试性。如果您使用特征/接口,您可以控制依赖项,但不能控制这些依赖项何时产生影响。
我在代码中提供了以下示例。
case class IO[+A](val ra: () => A){
def unsafePerformIO() : A = ra();
def map[B](f: A => B) : IO[B] = IO[B]( () => f(unsafePerformIO()))
def flatMap[B](f: A => IO[B]) : IO[B] = {
IO( () => f(ra()).unsafePerformIO())
}
}
case class Person(age: Int, name: String)
object Runner {
def getOlderPerson(p1: Person,p2:Person) : Person =
if(p1.age > p2.age)
p1
else
p2
def printOlder(p1: Person, p2: Person): IO[Unit] = {
IO( () => println(getOlderPerson(p1,p2)) ).map( x => println("Next") )
}
def printPerson(p:Person) = IO(() => {
println(p)
p
})
def main(args: Array[String]): Unit = {
val result = printPerson(Person(31,"Blair")).flatMap(a => printPerson(Person(23,"Tom"))
.flatMap(b => printOlder(a,b)))
result.unsafePerformIO()
}
}
你可以看到效果直到 main 函数才被执行,我觉得很酷。在观看视频后,我对此有了一些感觉,于是就想到了这个。
我的实现是否正确,我的理解是否正确。
我还在思考是否应该与 ValidationMonad 结合起来使用,例如 ValidationMonad[IO[Person]],这样当出现异常时就可以快速中断了。你怎么看?
Blair
ConsoleIO
部分。它演示了IO操作存在的声明(case object GetLine ...
,case class PutLine ...
)与如果运行这些操作可能发生的定义(implicit object ConsoleEffect ...
)之间的分离。但请注意,其中还有其他内容;它不是仅演示我所说的最小代码。 - Seth Tisue