Scala IO monad: what's the point?

喜夏-厌秋 提交于 2019-12-02 18:08:32
Seth Tisue

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”.

drexin

The benefit of using the IO monad is having pure programs. You do not push the side-effects higher up the chain, but eliminate them. If you have an impure function like the following:

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

You can remove the side-effect by rewriting it to:

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

The second function is referentially transparent.

A very good explanation why using the IO monads leads to pure programs can be found in Rúnar Bjarnason's slides from scala.io (video can be found here).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!