查看来自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]
)如何与map
或flatMap
保持一致?
这是本书中的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 }
}
答案 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 y
与a.flatMap(x => b.flatMap( y => return(y)))
完全相同,所以scala中的最后一个语句被理解为扩展为map。