Scala IO monad: what's the point?

后端 未结 2 848
半阙折子戏
半阙折子戏 2021-02-01 05:14

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

相关标签:
2条回答
  • 2021-02-01 05:34

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

    0 讨论(0)
  • 2021-02-01 05:41

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

    0 讨论(0)
提交回复
热议问题