简体   繁体   中英

flatMap and For-Comprehension with IO Monad

Looking at an IO Monad example from Functional Programming in Scala :

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

I decided to re-write converter with a flatMap .

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

When I replaced the last flatMap with map , I did not see the result of the readLine printed out on the console.

With flatMap :

enter a temperate in degrees 
37.0

With map :

enter a temperate in degrees

Why? Also, how is the signature ( IO[Unit] ) still the same with map or flatMap ?

Here's the IO monad from this book.

  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 }
  }

I think Scala converts IO[IO[Unit]] into the IO[Unit] in the second case. Try to run both variants in scala console, and don't specify type for the def converterFlatMap: IO[Unit] , and you'll see the difference.

As for why map doesn't work, it is clearly seen from the definition of IO: when you map over IO[IO[T]], map inside will call run only on the outer IO, result will be IO[IO[T]], so only first two PrintLine and ReadLine will be executed.

flatMap will also execute inner IO, and result will be IO[T] where T is the type parameter A of the inner IO , so all three of the statements will be executed.

PS: I think you incorrectly expanded for-comprehension. according to rules , for loop that you have written should be expanded to:

PrintLine("enter a temperate in degrees F").flatMap { case _ =>
    ReadLine.map(_.toDouble).flatMap { case d =>
        PrintLine((d + 32).toString).map { case _ => ()}
    }
}

Notice that in this version flatMaps/maps are nested.

PPS: In fact last for statement should be also flatMap, not map. If we assume that scala had a "return" operator that puts values into the monadic context,
(eg return(3) will create IO[Int] that does nothing and it's function run returns 3.),
then we can rewrite for (x <- a; y <- b) yield y as
a.flatMap(x => b.flatMap( y => return(y))) ,
but because b.flatMap( y => return(y)) works absolutely the same as b.map(y => y) last statement in the scala for comprehension is expanded into map.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM