What has helped me is to think of a monad as a container with operations (map/flatMap/filter/foreach) that can act on the contents without extracting it first.
For instance, this function “opens” the option to get its content:
def display(msg: String, prompt: Option[String] = None) = {
prompt match {
case None => ()
case Some(str) => print(str)
}
println(msg)
}
Contrast this with:
def display(msg: String, prompt: Option[String] = None) = {
prompt foreach print
println(msg)
}
where the option is told to print its content (if any).
I find it especially powerful with futures because in that case, there is no alternative: There is no way (short of using an additional thread) to “extract” the contents of a future that is still being computed asynchronously.
The beauty of Scala is the for/yield
construct that results in code much easier to read than chained calls to map
and flatMap
:
val futureImage: Future[Image] = ...
val futureText: Future[Text] = ...
val futurePage: Future[Page] =
for {
image <- futureImage
text <- futureText
} yield makePage(image, text)
The Java equivalent is harder to read:
CompletionStage<Image> futureImage = ...
CompletionStage<Text> futureText = ...
CompletionStage<Page> futurePage =
futureImage.thenCompose(image ->
futureText.thenApply(text ->
makePage(image, text)
));
(especially given than flatMap
is called thenCompose
and map
is called thenApply
on Java’s futures)