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
/map
call has that return type
Lessons learned
flatMap
extracts 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 <- getString
MAPS TOgetString.flatMap { a: String => ...
- thea
comes out of getString, and it's made available to the next lineb <- getString
MAPS TOgetString.flatMap { b: String => ...
- this anonymous function has access to [a,b]