Scala IO monad:有什么意义呢?

wbgh16ku  于 8个月前  发布在  Scala
关注(0)|答案(2)|浏览(73)

我最近看了一个视频,是关于如何提出IO monad的,这个视频是用scala讲的。实际上,我想知道让函数返回IO[A]的意义何在。封装在IO对象中的lambda表达式就是这些变化,在更高的层次上,它们必须被观察到,我的意思是被执行,这样就会发生一些事情。你不只是把问题推到更高的地方吗?
我能看到的唯一好处是,它允许延迟求值,也就是说,如果不调用unsafePerformIO操作,就不会产生副作用。此外,我猜程序的其他部分可以使用/共享代码,并决定何时希望副作用发生。
我在想,这是不是全部?可测试性有什么优势吗?我假设不是因为你必须观察到的效果,这有点否定这一点。如果你使用了traits / interfaces,你可以控制依赖关系,但不能控制依赖关系发生的时间。
我将下面的示例放在代码中。

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的,我想这很酷。我从视频中感受到这一点后想到了这个。
我的理解是否正确,我的理解是否正确。
我还想知道是否应该将milage与ValidationMonad结合起来,就像ValidationMonad[IO[Person]]一样,这样我们就可以在异常发生时短路?请思考。
布莱尔

ctzwtxfj

ctzwtxfj1#

函数的类型签名记录它是否有副作用是有价值的。您的IO实现具有价值,因为它确实完成了很多工作。它使您的代码更好地记录;如果你重构你的代码,尽可能地把涉及IO的逻辑和不涉及IO的逻辑分开,你就使得不涉及IO的函数更容易组合,更容易测试。您可以在没有显式IO类型的情况下进行相同的重构;但使用显式类型意味着编译器可以帮助您进行分离。
但这仅仅是个开始。在你的问题中的代码中,IO操作被编码为paddas,因此是不透明的;对于IO操作,除了运行它之外,你什么也做不了,而且运行时的效果是硬编码的。
这不是实现IO monad的唯一可能方法。
例如,我可能会让IO操作类扩展一个公共特征。然后,我可以编写一个测试,运行一个函数,看看它是否返回正确的IO操作。
在这些情况下,类代表不同类型的IO操作,我可能不会包含操作在运行时执行的硬编码实现。相反,我可以使用类型类模式将其解耦。这将允许交换IO操作的不同实现。例如,我可能有一组与生产数据库对话的实现,另一组与用于测试目的的模拟内存数据库对话。
在Bjarnason和Chiusano的书《Scala中的函数式编程》的第13章(“外部效应和I/O”)中有一个很好的处理这些问题的方法。特别参见13.2.2,“简单IO类型的优点和缺点”。
更新:re“swap in different implementations of what the IO actions do”,你可以查找“free monad”,这是一种安排的方法。同样相关的还有“无标记final”风格,在这种风格中,您可以独立于具体类型(如IO)编写一元代码。

aiqt4smr

aiqt4smr2#

使用IO monad的好处是拥有纯程序。你不会把副作用推到更高的链条上,而是消除它们。如果你有一个不纯的函数,如下所示:

def greet {
  println("What is your name?")
  val name = readLine
  println(s"Hello, $name!")
}

您可以通过将其重写为以下内容来删除副作用:

def greet: IO[Unit] = for {
  _ <- putStrLn("What is your name?")
  name <- readLn
  _ <- putStrLn(s"Hello, $name!")
} yield ()

第二个函数是引用透明的。
一个很好的解释为什么使用IO monad会导致纯程序可以从scala.io找到in Rúnar Bjarnason's slides(视频可以在here找到)。

相关问题