Example #13: Explain the concepts
Can you explain the Scala concepts applied in the following code snippets?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
object Test extends App { trait AList[C[_]] { def aFunction[T, R](f: T => R): C[T] => C[R] } implicit object MyList extends AList[List] { //returns a function: so this is a higher order function def aFunction[T, R](f: T => R): List[T] => List[R] = { list => println("then here.......") list.map(f) } } //AnyRef is like java.lang.Object implicit def generic[C[_], T](input: C[T]) = new AnyRef { // AnyRef is optional //takes a function as an arg: So, this is a higher order function def anotherFunction[R](f: T => R)(implicit listObj: AList[C]): C[R] = { println("In here first.......") val aClosure = listObj.aFunction(f) //MyList object aClosure(input) //pass the list to the closure } } val result = List(1, 2, 3) anotherFunction (x => x + 1) println(result); val result2 = List("Java", "Scala") anotherFunction (x => x.length) println(result2); } |
1. Higher Order Functions: “Line 10” is a higher order function as the function returns another function i.e. List[T] => List[R] is returned.
“Line 20” is a higher order function as the function takes another function as an input parameter i.e. T => R.
map, flatMap, filter, etc are higher order functions as they take functions as arguments.
2. Closures: “Line 10” is a closure as it closes over a “list”. The list is supplied in “Line 23” aClosure(input) //pass the list to the closure.
3. Generics: C[_], T, R, and C[T] are Scala generic type parameters. For example, C[T] can be substituted for List[Int] and can take List(1, 2, 3).
4. Implicits: The output of the above code is:
1 2 3 4 5 6 7 8 |
In here first....... then here....... List(2, 3, 4) In here first....... then here....... List(4, 5) |
The magic happens due to implicit invocations. The ” List(1, 2, 3) anotherFunction (x => x + 1)” invokes the function on “Line 20” inside the “AnyRef” object on “Line 17”, which takes “(input: C[T])” as an argument. “C[T]” can be substituted for the “List[Int]”.
5. Partial functions: “Line 20” is a partial function, which takes a custom list of type “AList”
1 2 3 |
(implicit listObj: AList[C]) |
Due to implicits, it gets substituted to “Line 8” object of type “MyList”, and invokes its function “aList”.
What is the benefit of the above code?
You could have just called a list class’s “map” function as in
1 2 3 |
List(1, 2, 3) map (x => x + 1) // same as List(1, 2, 3).map (x => x + 1) |
Firstly to demonstrate the Scala concepts with a simple example.
Secondly, the above code can be extended to be an abstraction (i.e. a functor) and handle other C[_] types like “Option[T]” as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
object Test extends App { trait AType[C[_]] { def aFunction[T, R](f: T => R): C[T] => C[R] } implicit object MyList extends AType[List] { //returns a function: so this is a higher order function def aFunction[T, R](f: T => R): List[T] => List[R] = { list => println("then here for a List type.......") list.map(f) } } implicit object MyOption extends AType[Option] { //returns a function: so this is a higher order function def aFunction[T, R](f: T => R): Option[T] => Option[R] = { anOption => println("then here for an Option type.......") anOption.map(f) } } //AnyRef is like java.lang.Object implicit def generic[C[_], T](input: C[T]) = new AnyRef { // AnyRef is optional //takes a function as an arg: So, this is a higher order function def anotherFunction[R](f: T => R)(implicit aObj: AType[C]): C[R] = { println("In here first.......") val aClosure = aObj.aFunction(f) // aClosure(input) //pass the list to the closure } } val result = List(1, 2, 3) anotherFunction (x => x + 1) println(result); val result2 = Option(5) anotherFunction (_ + 1) anotherFunction (_ + 2) println(result2); } |
The Output: will be
1 2 3 4 5 6 7 8 |
In here first....... then here for a List type....... List(2, 3, 4) In here first....... then here for an Option type....... Some(8) |
Q. What if you want to use Some(5) & None instead of “Option”?
A. You can define two functions that returns a type Option.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
object Test extends App { trait AType[C[_]] { def aFunction[T, R](f: T => R): C[T] => C[R] } implicit object MyList extends AType[List] { //returns a function: so this is a higher order function def aFunction[T, R](f: T => R): List[T] => List[R] = { list => println("then here for a List type.......") list.map(f) } } implicit object MyOption extends AType[Option] { //returns a function: so this is a higher order function def aFunction[T, R](f: T => R): Option[T] => Option[R] = { anOption => println("then here for an Option type.......") anOption.map(f) } } //AnyRef is like java.lang.Object implicit def generic[C[_], T](input: C[T]) = new AnyRef { // AnyRef is optional //takes a function as an arg: So, this is a higher order function def anotherFunction[R](f: T => R)(implicit aObj: AType[C]): C[R] = { println("In here first.......") val aClosure = aObj.aFunction(f) // aClosure(input) //pass the list to the closure } } val result = List(1, 2, 3) anotherFunction (x => x + 1) println(result); val result2 = some(5) anotherFunction (_ + 1) anotherFunction (_ + 2) println(result2); val result3 = none[Int] anotherFunction (_ + 1) println(result3); def some[T](input: T): Option[T] = Some(input) def none[T]: Option[T] = None } |
Pay attention to: lines 44 & 46 where new functions were added to handle Some(x) and None respectively via functions. Lines 38 to 42 make use of these functions.
Output:
1 2 3 4 5 6 7 8 9 10 11 |
List(2, 3, 4) In here first....... then here for an Option type....... In here first....... then here for an Option type....... Some(8) In here first....... then here for an Option type....... None |