Scala IO monad:有什么意义? [英] Scala IO monad: what's the point?

查看:119
本文介绍了Scala IO monad:有什么意义?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近观看了一个关于如何提出IO monad的视频,谈话内容是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)

对象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()
}

}

你可以看到效果如何延迟到主要我认为很酷。我从视频中了解到这一点后就提出了这个问题。



我的执行是否正确,并且我的理解是正确的。



我也想知道是否应该使用milage与ValidationMonad结合使用,就像ValidationMonad [IO [Person]]一样,所以当发生异常时我们可以短路。请注意。



Blair

解决方案

记录功能的签名以记录它是否有副作用。您的IO实现具有价值,因为它确实实现了很多。它使您的代码更好地记录在案;如果您重构代码以尽可能多地分离涉及IO的逻辑,那么您已经使得非IO涉及的函数更易于组合并且更易于测试。你可以在没有显式IO类型的情况下进行相同的重构。但使用显式类型意味着编译器可以帮助你做分离。



但这只是开始。在你的问题的代码中,IO操作被编码为lambdas,因此是不透明的;除了运行IO操作之外,没有任何事情可以用IO操作完成,运行时的影响是硬编码的。



这不是实现IO monad的唯一可能方式。



例如,我可能会让我的IO操作案例类扩展一个共同的特征。然后,我可以编写一个运行函数的测试,并查看它是否返回IO动作的正确类型。



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



Bjarnason& amp; amp;& amp; amp; amp; gt;< 1>的第13章(外部效应和I / O Chiusano的书。尤其参见13.2.2简单IO类型的优点和缺点。

UPDATE(2015年12月):交换IO操作的不同实现,这些日子里越来越多的人正在使用自由单体这种东西;见例如John De Goes的博客文章 FP的现代建筑


I watched a video recently on how you could come up with the IO monad, the talk was in scala. I am actually wondering what the point of having functions return IO[A] out of them. The lambda expressions wrapped in the IO object are what the mutations are and at some point higher up the change they have to be observed, I mean executed, so that something happens. Are you not just pushing the problem higher up the tree somewhere else?

The only benefit I can see is that it allows for lazy evaluation, in the sense that if you do not call the unsafePerformIO operation no side effects occur. Also I guess other parts of the program could use / share code and deciede when it wants the side effects to occur.

I was wondering if this is all? Is there any advantages in testability? I am assuming not as you would have to observe the effects which sort of negates this. If you used traits / interfaces you can control the dependencies but not when the effects take place on these dependencies.

I put together the following example in code.

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()
  }

}

You can see how the effects are deferred until main which I guess is cool. I came up with this after getting a feel for this from the video.

Is my implementation correct and Is my understanding correct.

I am also wondering whether to get milage it should be combined with the ValidationMonad, as in ValidationMonad[IO[Person]] so we can short circuit when exceptions occurs? Thoughts please.

Blair

解决方案

It is valuable for the type signature of a function to record whether or not it has side effects. Your implementation of IO has value because it does accomplish that much. It makes your code better documented; and if you refactor your code to separate, as much as possible, logic which involves IO from logic that doesn't, you've made the non-IO-involving functions more composable and more testable. You could do that same refactoring without an explicit IO type; but using an explicit type means the compiler can help you do the separation.

But that's only the beginning. In the code in your question, IO actions are encoded as lambdas, and therefore are opaque; there is nothing you can do with an IO action except run it, and its effect when run is hardcoded.

That is not the only possible way to implement the IO monad.

For example, I might make my IO actions case classes that extend a common trait. Then I can, for example, write a test that runs a function and sees whether it returns the right kind of IO action.

In those case classes representing different kinds of IO actions, I might not include hard coded implementations of what the actions do when I run. Instead, I could decouple that using the typeclass pattern. That would allow swapping in different implementations of what the IO actions do. For example, I might have one set of implementations that talk to a production database, and another set that talks to a mock in-memory database for testing purposes.

There is a good treatment of these issues in Chapter 13 ("External Effects and I/O") of Bjarnason & Chiusano's book Functional Programming in Scala. See especially 13.2.2, "Benefits and drawbacks of the simple IO type".

UPDATE (December 2015): re "swap in different implementations of what the IO actions do", these days more and more people are using the "free monad" for this kind of thing; see e.g. John De Goes's blog post "A Modern Architecture for FP".

这篇关于Scala IO monad:有什么意义?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆