Without much explanation today, here are a couple of source code examples from my book, Functional Programming, Simplified (in Scala). The only thing I’ll say about this code is that I created it in the process of writing that book, and the examples show how the Scala compiler translates for-expressions into map and flatMap calls behind the scenes.
1) How Options in for-expressions convert to map and flatMap
This for-expression:
val x = for {
i <- Option(1)
j <- Option(2)
k <- Option(3)
} yield i + j + k
is the same as this use of flatMap and map:
val x: Option[Int] = Option.apply[Int](1).flatMap[Int] { (i: Int) =>
Option.apply[Int](2).flatMap[Int] { (j: Int) =>
Option.apply[Int](3).map[Int] { (k: Int) =>
i.+(j).+(k)
}
}
}
which is essentially the same as this code:
val x: Option[Int] = Some(1).flatMap { (i: Int) =>
Some(2).flatMap { (j: Int) =>
Some(3).map { (k: Int) =>
i + j + k
}
}
}
As a side note, if you haven’t worked with flatMap on Options yet, it can help to know that flatMap’s function should return an Option, like this:
Some(1).flatMap{ i => Option(i) }
2) How a Scala IO Monad converts (for-expression to map and flatMap)
Given these IO functions:
def getLine: IO[String] = IO(scala.io.StdIn.readLine());
def putStrLn(s: String): IO[Unit] = IO(println(s));
This for-expression that uses the IO Monad:
def doBlock: IO[Unit] = for {
_ <- putStrLn("First name?")
firstName <- getLine
_ <- putStrLn(s"first: $firstName")
} yield Unit
doBlock.run
compiles to this map/flatMap code:
def doBlock: IO[Unit] = putStrLn("First name?").flatMap {_ =>
getLine.flatMap { firstName =>
putStrLn(StringContext("first: ", "").s(firstName)).map { _ =>
Unit
}
}
};
doBlock.run
This is what that map/flatMap code looks like with its data types:
def doBlock: IO[Unit] = putStrLn("First name?").flatMap[Unit] { _: Unit =>
getLine.flatMap[Unit] { firstName: String =>
putStrLn(new StringContext("first: ", "").s(firstName)).map[Unit]{ _: Unit =>
scala.Unit
}
}
}
- notice that the
flatMap’s have the return typeUnit - that’s because the block has the return type
Unit, or more accurately, the lastflatMap/mapcall has that return type
Lessons learned
flatMapextracts the values out of monads- this code:
_ <- putStrLn("First name?") - is equivalent to this code:
putStrLn("First name?").flatMap {_ => - this code:
firstName <- getLine - is equivalent to this:
getLine.flatMap[Unit] { firstName: String =>
3) Another IO Monad + for expression example
This code:
def getString: IO[String] = IO("yo ") //returns the type `IO` (a monad)
def doBlock: IO[String] = for {
a <- getString
b <- getString
c <- getString
} yield a + b + c
compiles to this code:
def getString: IO[String] = IO.apply[String]("yo ")
def doBlock: IO[String] = getString.flatMap[String] { a: String =>
getString.flatMap[String] { b: String =>
getString.map[String] { c: String =>
a + b + c
}
}
}
Notes
- again, `flatMap` pulls the values out of the IO monad
- getString yields `IO("yo ")`
- getString.flatMap yields `a: String`
- to the right of each flatMap invocation is an anonymous function
- flatMap passes the unwrapped value into that anonymous function
- `a` and `b` are passed along until the final `getString` uses all of
`a`, `b`, and `c`
- `a` and `b` are essentially in the scope of the final `map` method’s
anonymous function
| this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
4) Mapping a for-expression to map and flatMap
One more example of how a for-expression translates to map and flatMap calls:
def doBlock: IO[String] = for {
a <- getString //getString.flatMap[String] { a: String => ...
b <- getString //[a] => getString.flatMap[String] { b: String => ...
c <- getString //[a,b] => getString.map[String] { c: String => a + b + c }
} yield a + b + c
- the map and flatMap invocations each yield a String
- Strings are passed down the chain as a, b, and c
- Strings are passed back up the chain
a <- getStringMAPS TOgetString.flatMap { a: String => ...- theacomes out of getString, and it's made available to the next lineb <- getStringMAPS TOgetString.flatMap { b: String => ...- this anonymous function has access to [a,b]