处理IO Monad FP中不纯净的副作用

jak*_*ack 4 monads functional-programming scala

试图了解如何最好地应对FP中的副作用。

我实现了这个基本的IO实现:

  trait IO[A] {
    def run: A
  }
  object IO {
    def unit[A](a: => A): IO[A] = new IO[A] { def run = a }
    def loadFile(fileResourcePath: String) = IO.unit[List[String]]{ 
        Source.fromResource(fileResourcePath).getLines.toList }
    def printMessage(message: String) = IO.unit[Unit]{ println(message) }
    def readLine(message:String) = IO.unit[String]{ StdIn.readLine() }
  }
Run Code Online (Sandbox Code Playgroud)

我有以下用例:

- load lines from log file
- parse each line to BusinessType object
- process each BusinessType object
- print process result
Run Code Online (Sandbox Code Playgroud)

情况1:所以Scala代码可能看起来像这样

val load: String => List[String]
val parse: List[String] => List[BusinessType]
val process: List[BusinessType] => String
val output: String => Unit
Run Code Online (Sandbox Code Playgroud)

情况2:我决定在上面使用IO:

val load: String => IO[List[String]]
val parse: IO[List[String]] => List[BusinessType]
val process: List[BusinessType] => IO[Unit]
val output: IO[Unit] => Unit
Run Code Online (Sandbox Code Playgroud)

在情况1中,加载是不纯的,因为它是从文件中读取的,所以输出也是不纯的,因为它是将结果写入控制台。

为了更加实用,我使用了案例2。

问题:

- Aren't case 1 and 2 really the same thing?
- In case 2 aren't we just delaying the inevitable? 
  as the parse function will need to call the io.run 
  method and cause a side-effect?
- when they say "leave side-effects until the end of the world" 
  how does this apply to the example above? where is the 
  end of the world here?
Run Code Online (Sandbox Code Playgroud)

Thi*_*ilo 6

您的IO monad似乎缺少所有monad内容,即您可以flatMap通过它来从较小的IO中构建更大的IO的部分。这样,一切都将保持“纯净”,直到通话run结束为止。

如果不是第二种情况,我们是否只是延迟不可避免的事情?由于解析功能将需要调用io.run方法并引起副作用?

否。该parse函数不应调用io.run。它应该返回另一个IO,然后可以将其与其输入IO组合在一起。

当他们说“将副作用留到世界的尽头”时,这对上面的示例有何影响?世界的尽头在哪里?

世界末日将是您的程序要做的最后一件事。你只有run一次。程序的其余部分“纯粹”为此构建了一个巨型IO。

就像是

def load(): IO[Seq[String]]    
def parse(data: Seq[String]): IO[Parsed]  // returns IO, because has side-effects
def pureComputation(data: Parsed): Result  // no side-effects, no need to use I/O
def output(data: Result): IO[Unit]

// combining effects is "pure", so the whole thing
// can be a `val` (or a `def` if it takes some input params)
val program: IO[Unit] = for {
      data <- load()    // use <- to "map" over IO
      parsed <- parse()
      result = pureComputation(parsed)  // = instead of <-, no I/O here 
      _ <- output(result)
   } yield ()

// only `run` at the end produces any effects
def main() {
   program.run()
}
Run Code Online (Sandbox Code Playgroud)