Scala IO monad:有什么意义?

Bla*_*son 23 monads functional-programming dependency-injection scala

我最近观看了一段关于你如何能够提出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()
  }

}
Run Code Online (Sandbox Code Playgroud)

您可以看到效果如何推迟到主要我认为很酷.在从视频中感受到这一点后,我想出了这个.

我的实施是否正确,我的理解是否正确.

我也想知道是否应该将其与ValidationMonad结合使用,如ValidationMonad [IO [Person]]所以我们可以在发生异常时短路?想一想.

布莱尔

Set*_*sue 28

对于函数的类型签名来说,记录它是否具有副作用是有价值的.您的IO实现具有价值,因为它确实实现了这一点.它使您的代码更好地记录; 如果您重构代码以尽可能地将逻辑从逻辑中分离出来而不是逻辑,那么您已经使非IO涉及的函数更易于组合和更易于测试.您可以在没有显式IO类型的情况下进行相同的重构; 但使用显式类型意味着编译器可以帮助您进行分离.

但那只是一个开始.在您的问题的代码中,IO操作被编码为lambda,因此是不透明的; 除了运行它之外,你无法用IO动作做任何事情,并且在运行时它的效果是硬编码的.

这不是实现IO monad的唯一可行方法.

例如,我可能会使我的IO操作案例类扩展一个共同的特征.然后我就可以,例如,写一个运行的功能,并认为它是否返回正确的测试样的 IO行动.

在那些表示不同类型的IO操作的case类中,我可能不会包含操作时操作所执行操作的硬编码实现.相反,我可以使用类型类模式将其解耦.这将允许交换IO动作的不同实现.例如,我可能有一组与生产数据库通信的实现,另一组与模拟内存数据库通信以进行测试.

在Bjarnason&Chiusano的书" Scala中的函数式编程"一书的第13章("外部效应和I/O")中,对这些问题进行了很好的处理.特别参见13.2.2,"简单IO类型的优点和缺点".

更新(2015年12月):重新"交换IO行为的不同实现",现在越来越多的人使用"免费monad"来做这类事情; 参见John De Goes的博客文章" FP的现代建筑 ".

  • 请参阅Drexin链接的Runar幻灯片,特别是"ConsoleIO"的内容.它演示了IO操作存在的声明的分离(`case object GetLine ...`,`case class PutLine ...`),从定义运行这些操作时可能发生的事情(`隐式对象ConsoleEffect .. .`).但请注意那里还有其他东西; 它不是最小的代码,只能说明我说的话. (2认同)

dre*_*xin 19

使用IO monad的好处是拥有纯粹的程序.你不会把副作用推到链条的上方,而是消除它们.如果你有一个不纯的功能,如下所示:

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

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

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

第二个功能是参考透明的.

一个很好的解释为什么使用IO monads导致纯程序可以在 RúnarBjarnason的scala.io 幻灯片中找到(视频可以在这里找到).