In Scala, lots of the things are kind of a magic for Java developers. Sometimes this magic amazes the code but sometimes it has ruined the developer’s life. Today we are going to discuss one of the magic called “For Expression“, “For Comprehension” or as per Java developers like me called “For Loop“.
Scala developers familiar with for comprehension and most of them have the idea, how this for comprehension is going to work. Internally, it converts expressions into map, flatMap
kind of stuff. Some of the developers differentiate the for expression into two categories as:
- For Loop
for ( i <- generator ) { statements }
- For Comprehension
for ( i <- generator; a = definition; if (filter)) yield a
But according to my investigation, above these are the same and the only difference is what we call. For me, Scala “for” is an expression because of functional behavior everything is an expression. Rest of the things depends on you, what you like to call the “for” expression.
Now come to the point, Scala for
expression has three properties:
p <- persons // A Generator
n = p.name // A Definition
if(n startsWith "Some") // A Filter
How we executefor expression
on custom collection classes or a custom wrapper like Option ???
It’s Simple:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
scala> import scala.collection.mutable.ArrayBuffer | |
import scala.collection.mutable.ArrayBuffer | |
scala> case class CustomList[A](elements: A*) { | |
| | |
| val elems = new ArrayBuffer[A] | |
| elems ++= elements | |
| } | |
defined class CustomList | |
scala> val list = CustomList(1, 2, 3) | |
list: CustomList[Int] = CustomList(WrappedArray(1, 2, 3)) | |
scala> for( i <– list) { println(i) } | |
<console>:14: error: value foreach is not a member of CustomList[Int] | |
for( i <– list) { println(i) } |
Oooopppsss, we are getting the error.
But the interesting point is, the error is user-friendly. Error teach us, foreach
is missing that’s why I am not running because compiler converts for expression
into map, flatMap, withFilter and foreach
methods.
According to this error, now we are sure simple for loop also act as a for expression. Or if you guys want some more detail, use `scalac -Xprint:all file.scala` option which shows you, when compiler converts for loop into methods.
If you want to execute for expression with your custom collection class or wrapper, you need to implement following methods:
def foreach(c: A => Unit): Unit
def map[B](f: A => B): CustomList[B]
def withFilter(p: A => Boolean): CustomList[A]
def flatMap[B](f: A => CustomList[B]): CustomList[B]
FAQ: For Expression With Custom Collection
Q. How can use, one generator and print the values like `for( i <- ints) { println(i)}` ???
Ans: For that, we must implement the methodforeach
in our custom collection class.
def foreach(c: A => Unit): Unit = { elems.foreach(c) }
Q. How can I use, Definition and generator together in for expression ???
Ans: For that, we must need to implement map
method as well. Without,map
we are not able to use definition part in our for expression.
def map[B](f: A => B): CustomList[B] = { val temp = elems.map(f) CustomList(temp: _*) }
Q. If we want to use for/yield, what methods need to declare???
Ans. This depends on your requirements. If you are going to use only one generator there is only map
method is required, but for multiple generators, without flatMap
you are getting the error.
Q. For multiple generators, how we declare flatMap
???
Ans. It’s really simple
def flatMap[B](f: A => CustomList[B]): CustomList[B] = { val temp = elems.map(f) flatternLike(temp) } def flatternLike[B](seq: Seq[CustomList[B]]): CustomList[B] = { val temp = new ArrayBuffer[B] for(i <- seq) { for(j <- i) { temp += j } } CustomList(temp: _*) }
Q. Normally in the collection, we are using filter
method but here we need to use withFilter
, why ???
Ans. Because filter
follows the strict rule but withFilter
act as a lazy. While we are using filter, it always returns new collection but withFilter evaluates lazily and check the elements one by one. This may be something like confusing, but you will look into that:
- https://stackoverflow.com/questions/19617378/withfilter-instead-of-filter/19618241
- https://alvinalexander.com/scala/examples-shows-differences-between-strict-lazy-evaluation-in-scala
Q. Every time, when we need to create the custom collection and used it with for expression, do we need to implement all four methods ???
Ans. No, it’s not true. This depends on your requirement as below:
- Need to use only for loop without yield
// foreach
- Need to use one generator with yield
// map
- Need to use multiple generators without yield
//foreach
- Need to use multiple generators with yield
// foreach, map, and flatMap
- Need to use one generator without yield and with Definition
// foreach, and map
- Need to use one generator with yield and definition
// map
- Need to use one generator without yield with filter
// withFilter
There is multiple combinations will be created according to your requirement, I am sure, now you have some decent idea.
Q. You have any running full example for custom collection with for expressions ???
Ans. Yes we have
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Custom Collection */ | |
import scala.collection.mutable.ArrayBuffer | |
case class CustomList[A](elements: A*) { | |
val elems = new ArrayBuffer[A] | |
elems ++= elements | |
def foreach(c: A => Unit): Unit = { | |
elems.foreach(c) | |
} | |
def map[B](f: A => B): CustomList[B] = { | |
val temp = elems.map(f) | |
CustomList(temp: _*) | |
} | |
def withFilter(p: A => Boolean): CustomList[A] = { | |
val temp = elems.filter(p) | |
CustomList(temp: _*) | |
} | |
def flatMap[B](f: A => CustomList[B]): CustomList[B] = { | |
val temp = elems.map(f) | |
flatternLike(temp) | |
} | |
def flatternLike[B](seq: Seq[CustomList[B]]): CustomList[B] = { | |
val temp = new ArrayBuffer[B] | |
for(i <– seq) { | |
for(j <– i) { | |
temp += j | |
} | |
} | |
CustomList(temp: _*) | |
} | |
} | |
/* MAIN */ | |
object ForExpression extends App { | |
val ints = CustomList(1, 2, 3) | |
for( i <– ints) { println(i)} | |
val values1: CustomList[Int] = for(i <– ints; a = (i + 1)) yield a | |
println(s"Map Values: $values1") | |
val values2 = for { | |
i <– ints | |
if(i % 2 == 0) | |
} yield i | |
println(s"With Filter Values: $values2") | |
val values3: CustomList[Int] = for(i <– ints; j <– ints; a = (i + j + 1)) yield a | |
println(s"FlatMap Values: $values3") | |
} |
Q. Yes, that will be a good example, what about custom wrapper ??? As you told, we can use for expression with custom wrapper as well like,Option
you have an example for that ???
Ans. Yes, we have, But we will discuss that on our next blog 🙂
Reblogged this on Harmeet Singh(Taara).
Reblogged this on Coding, Unix & Other Hackeresque Things.