Back2Basics: For Expression Served From Scala Magic Box – I.

Reading Time: 3 minutes

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:

  1. p <- persons            // A Generator
  2. n = p.name             // A Definition
  3. 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) }
view raw

forexp.scala

hosted with ❤ by GitHub

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:

  1. def foreach(c: A => Unit): Unit
  2. def map[B](f: A => B): CustomList[B]
  3. def withFilter(p: A => Boolean): CustomList[A]
  4. 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:

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")
}
view raw

forexp2.scala

hosted with ❤ by GitHub

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 🙂


knoldus-advt-sticker


 

Written by 

Harmeet Singh is a lead consultant, with experience of more than 5 years. He has expertise in Scala, Java, JVM, and functional programming. On a personal front; he is a food lover.

2 thoughts on “Back2Basics: For Expression Served From Scala Magic Box – I.4 min read

Comments are closed.