In scala, flatMap operation is used whenever there is a need to do a mapping followed by flattening. You would find code that maps an input to output1, which then maps to output2, and so on till you get the final output.
Demonstrative code as below
object MyClass {
def twice(x: Int) = Some(x * 2)
def thrice(x: Int) = Some(x * 3)
def quadruple(x: Int) = Some(x * 4)def main(args: Array[String]) {
List(1,2,3)
.flatMap(twice)
.flatMap(thrice)
.flatMap(quadruple)
.foreach(println)
}24
48
72
for comprehension is syntactic sugar that achieves the same result. The same scenario coded as a for comprehension would look as below.
object MyClass {
def twice(x: Int) = Some(x * 2)
def thrice(x: Int) = Some(x * 3)
def quadruple(x: Int) = Some(x * 4)def main(args: Array[String]) {
List(1,2,3)
.flatMap(twice)
.flatMap(thrice)
.flatMap(quadruple)
.foreach(println)
println("For comprehension")
(for {
n <- List(1,2,3)
t <- twice(n)
th <- thrice(t)
q <- quadruple(th)
} yield q).foreach(println)
}
}24
48
72
For comprehension
24
48
72
The same syntactical approach can also be used for custom scala classes as demonstrated below.
object MyClass {
case class Apple(s:String) {
override def toString() = s
}
def twice(x: Apple) = Some(Apple(x.toString + "-twice"))
def thrice(x: Apple) = Some(Apple(x.toString + "-thrice"))
def quadruple(x: Apple) = Some(Apple(x.toString + "-quadrupled"))def main(args: Array[String]) {
List(Apple("1"),Apple("2"),Apple("3"))
.flatMap(twice)
.flatMap(thrice)
.flatMap(quadruple)
.foreach(println)
println("For comprehension")
(for {
n <- List(Apple("1"),Apple("2"),Apple("3"))
t <- twice(n)
th <- thrice(t)
q <- quadruple(th)
} yield q).foreach(println)
}
}1-twice-thrice-quadrupled
2-twice-thrice-quadrupled
3-twice-thrice-quadrupled
For comprehension
1-twice-thrice-quadrupled
2-twice-thrice-quadrupled
3-twice-thrice-quadrupled
I think it is a matter of style/preference on whether flatMap syntax of for-comprehension syntax is better.
Aligning the types for methods within a for-comprehension
When chaining flatMap
functions as described above, it is obvious that the output from the earlier function has to match to the input of the next function. If not, the chaining would not work and compiler would catch it.
Equivalently, all the left arrow operators within a for-comprehension have to align in type as well.
object MyClass {
case class Apple(s:String) {
override def toString() = s
}
case class Orange(s : String) {
override def toString() = s
}
def twice(x: Apple) = Some(Apple(x.toString + "-twice"))
def thrice(x: Apple) = Some(Apple(x.toString + "-thrice"))
def quadruple(x: Orange) = Some(Orange(x.toString + "-quadrupled"))def main(args: Array[String]) {
List(Apple("1"),Apple("2"),Apple("3"))
.flatMap(twice)
.flatMap(thrice)
.flatMap(quadruple)
.foreach(println)
}
}jdoodle.scala:22: error: type mismatch;
found : Main.Orange => Some[Main.Orange]
required: Main.Apple => scala.collection.IterableOnce[?]
.flatMap(quadruple) ^
The quadruple function takes in an Orange where as thrice returns an Apple. So the compiler complains as above.
The same error would be thrown when used within a for-comprehension as well
object MyClass {
case class Apple(s:String) {
override def toString() = s
}
case class Orange(s : String) {
override def toString() = s
}
def twice(x: Apple) = Some(Apple(x.toString + "-twice"))
def thrice(x: Apple) = Some(Apple(x.toString + "-thrice"))
def quadruple(x: Orange) = Some(Orange(x.toString + "-quadrupled"))def main(args: Array[String]) {
println("For comprehension")
(for {
n <- List(Apple("1"),Apple("2"),Apple("3"))
t <- twice(n)
th <- thrice(t)
q <- quadruple(th)
} yield q).foreach(println)
}
}jdoodle.scala:25: error: type mismatch;
found : Main.Apple
required: Main.Orange
q <- quadruple(th) ^