【问题标题】:Dealing with impure side-effects in FP, IO Monad在 FP、IO Monad 中处理不纯的副作用
【发布时间】:2019-10-03 18:48:00
【问题描述】:

试图了解如何最好地处理 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() }
  }

我有以下用例:

- load lines from log file
- parse each line to BusinessType object
- process each BusinessType object
- print process result

案例 1: 所以 Scala 代码可能看起来像这样

val load: String => List[String]
val parse: List[String] => List[BusinessType]
val process: List[BusinessType] => String
val output: String => Unit

案例 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

在情况 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?

【问题讨论】:

    标签: scala functional-programming monads


    【解决方案1】:

    您的 IO monad 似乎缺少所有 monad 的东西,即您可以 flatMap 覆盖它以从较小的 IO 构建更大的 IO 的部分。这样,在最后调用 run 之前,一切都保持“纯净”。

    在情况 2 中,我们不只是推迟不可避免的事情吗? 因为解析函数需要调用 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()
    }
    

    【讨论】:

    • 所以 "val parse: IO[List[String]] => List[BusinessType]" 看起来像这样然后 "val parse: IO[List[String]] => IO[List[BusinessType ]]"?
    • 不,应该是def parse(in: List[String]): IO[List[BusinessType]]IO 只出现在结果端(如果有的话,如果没有副作用,它可以只是List[BusinessType]。那么如何从之前步骤中产生它的 IO 中获取输入?你flatMap: stepOne.flatMap( in: List[String] =&gt; parse(in))flatMap 为您“解包”数据。
    猜你喜欢
    • 1970-01-01
    • 2018-05-06
    • 1970-01-01
    • 2018-05-05
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多