使用IO Monad进行flatMap和For-Comprehension

时间:2014-02-15 16:54:04

标签: scala monads

查看来自Functional Programming in Scala的IO Monad示例:

def ReadLine: IO[String] = IO { readLine }
def PrintLine(msg: String): IO[Unit] = IO { println(msg) }

def converter: IO[Unit] = for {
  _ <- PrintLine("enter a temperature in degrees fahrenheit")
  d <- ReadLine.map(_.toDouble)
  _ <- PrintLine((d + 32).toString)
} yield ()

我决定用converter重新编写flatMap

def converterFlatMap: IO[Unit] = PrintLine("enter a temperate in degrees F").
     flatMap(x => ReadLine.map(_.toDouble)).
       flatMap(y => PrintLine((y + 32).toString))

当我用flatMap替换最后一个map时,我没有看到控制台上打印出readLine的结果。

使用flatMap

enter a temperate in degrees 
37.0

使用map

enter a temperate in degrees

为什么呢?此外,签名(IO[Unit])如何与mapflatMap保持一致?

这是本书中的IO monad。

  sealed trait IO[A] { self =>
    def run: A
    def map[B](f: A => B): IO[B] =
      new IO[B] { def run = f(self.run) }
    def flatMap[B](f: A => IO[B]): IO[B] =
      new IO[B] { def run = f(self.run).run }
  }

1 个答案:

答案 0 :(得分:5)

我认为Scala在第二种情况下将IO [IO [Unit]]转换为IO [Unit]。尝试在scala控制台中运行这两个变体,并且不要为def converterFlatMap: IO[Unit]指定类型,您将看到差异。

至于为什么地图不起作用,从IO的定义可以清楚地看出: 当你映射IO [IO [T]]时,map里面只会在外部IO上调用run,结果将是IO [IO [T]],所以只有前两个PrintLine和{{ 1}}将被执行。

flatMap也将执行内部IO,结果将为ReadLine,其中IO[T]是内部T的类型参数A,因此所有三个语句都将被执行。

P.S。:我认为你错误地扩展了理解力。根据{{​​3}},您编写的for循环应扩展为:

IO

请注意,在此版本中,flatMaps / maps是嵌套的。

P.P.S:事实上,最后PrintLine("enter a temperate in degrees F").flatMap { case _ => ReadLine.map(_.toDouble).flatMap { case d => PrintLine((d + 32).toString).map { case _ => ()} } } 语句也应该是flatMap,而不是map。如果我们假设scala有一个“返回”运算符,它将值放入monadic上下文中,
(例如,return(3)将创建不执行任何操作的IO [Int],它的函数for返回3.),然后我们可以将run重写为{{1} },
但因为for (x <- a; y <- b) yield ya.flatMap(x => b.flatMap( y => return(y)))完全相同,所以scala中的最后一个语句被理解为扩展为map。