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 34 35 36 37 38 39 40 |
package com.myapp object Test2 extends App { trait Wrapper[C[_]] { def aFunction[T,R](f: T => R): C[T] => C[R] } implicit object ListWrapper extends Wrapper[List] { //returns a function: so this is a higher order function override def aFunction[T,R](f: T => R): List[T] => List[R] = { l => println("taking T => R & returning List[T] => List[R]") l.map(f) } } // A function that returns an anonymous Object // Anonymous class Test2$$anon$1 AnyRef is like java.lang.Object private implicit def fnGenAnonymousObj[C[_], T](input: C[T]) = new { // or new AnyRef or new Object //takes a function as an arg: So, this is a higher order function def anotherFunction[R](f: T => R)(implicit wrap: Wrapper[C]): C[R] = { println("In here first.......") val aClosure: C[T] => C[R] = wrap.aFunction(f) // C[T] => C[R] val result: C[R] = aClosure(input) // pass the list to the closure result } } //val result = fnGenAnonymousObj(List(1, 2, 3)).anotherFunction {x => x + 1}(ListWrapper) // without implicits val result = List(1, 2, 3).anotherFunction {x => x + 1} println(result) //val result2 = fnGenAnonymousObj(List("Java", "Scala")).anotherFunction {x => x.length}(ListWrapper) // without implicits val result2 = List("Java", "Scala").anotherFunction {x => x.length} // with implicits println(result2) } |
Output:
1 2 3 4 5 6 7 8 |
In here first....... taking T => R & returning List[T] => List[R] List(2, 3, 4) In here first....... taking T => R & returning List[T] => List[R] List(4, 5) |
1. Higher Order Functions: “Line 12” is a higher order function as the function not only takes a function as an argument i.e. f: T => R, but also returns another function i.e. List[T] => List[R] via l => l.map(f).
“Line 23” is a higher order function as the function takes another function as an input argument i.e. T => R.
map, flatMap, filter, etc are higher order functions as they take functions as arguments.
2. Closures: “Line 12” is a closure as it closes over a “list”. The list is supplied in “Line 26” 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) & C[_] is a higher kinded type that can be substituted for List[List[Int]], List[Option[String]], List[List[List[String]]], etc.
4. Implicits: are used where the compiler rewrites
1 2 3 |
val result = List(1, 2, 3).anotherFunction {x => x + 1} |
TO:
1 2 3 |
val result = fnGenAnonymousObj(List(1, 2, 3)).anotherFunction {x => x + 1}(ListWrapper) |
The magic happens due to implicit invocations. The ” List(1, 2, 3) anotherFunction {x => x + 1}” invokes the function on “Line 23” inside the “AnyRef” object on “Line 20”, which takes “(input: C[T])” as an argument. “C[T]” can be substituted for the “List[Int]”. Similarly, “implicit wrap: Wrapper[C]” on line 23 invokes “aFunction” via ListWrapper on Line 10, which is of type Wrapper..
5. Partial functions: “Line 25” is a partial function.
How will you rewrite the above code without implicits?
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 |
package com.myapp object Test2 extends App { trait Wrapper[C[_]] { def aFunction[T,R](f: T => R): C[T] => C[R] } object ListWrapper extends Wrapper[List] { //returns a function: so this is a higher order function override def aFunction[T,R](f: T => R): List[T] => List[R] = { l => println("taking T => R & returning List[T] => List[R]") l.map(f) } } // A function that returns an anonymous Object // Anonymous class Test2$$anon$1 AnyRef is like java.lang.Object private def fnGenAnonymousObj[C[_], T](input: C[T]) = new { // or new AnyRef or new Object //takes a function as an arg: So, this is a higher order function def anotherFunction[R](f: T => R)(wrap: Wrapper[C]): C[R] = { println("In here first.......") val aClosure: C[T] => C[R] = wrap.aFunction(f) // C[T] => C[R] val result: C[R] = aClosure(input) // pass the list to the closure result } } val result = fnGenAnonymousObj(List(1, 2, 3)).anotherFunction {x => x + 1}(ListWrapper) //val result = List(1, 2, 3).anotherFunction {x => x + 1} // with implicits println(result) val result2 = fnGenAnonymousObj(List("Java", "Scala")).anotherFunction {x => x.length}(ListWrapper) //val result2 = List("Java", "Scala").anotherFunction {x => x.length} // with implicits println(result2) } |
Output:
1 2 3 4 5 6 7 8 9 |
In here first....... taking T => R & returning List[T] => List[R] List(2, 3, 4) In here first....... taking T => R & returning List[T] => List[R] List(4, 5) |
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 43 44 45 46 |
package com.myapp object Test2 extends App { trait Wrapper[C[_]] { def aFunction[T,R](f: T => R): C[T] => C[R] } implicit object ListWrapper extends Wrapper[List] { //returns a function: so this is a higher order function override def aFunction[T,R](f: T => R): List[T] => List[R] = { l => println("taking T => R & returning List[T] => List[R]") l.map(f) } } implicit object OptionWrapper extends Wrapper[Option] { //returns a function: so this is a higher order function override def aFunction[T,R](f: T => R): Option[T] => Option[R] = { l => println("taking T => R & returning Option[T] => Option[R]") l.map(f) } } // A function that returns an anonymous Object // Anonymous class Test2$$anon$1 AnyRef is like java.lang.Object private implicit def fnGenAnonymousObj[C[_], T](input: C[T]) = new { // or new AnyRef or new Object //takes a function as an arg: So, this is a higher order function def anotherFunction[R](f: T => R)(implicit wrap: Wrapper[C]): C[R] = { println("In here first.......") val aClosure: C[T] => C[R] = wrap.aFunction(f) // C[T] => C[R] val result: C[R] = aClosure(input) // pass the list to the closure result } } 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 9 10 |
In here first....... taking T => R & returning List[T] => List[R] List(2, 3, 4) In here first....... taking T => R & returning Option[T] => Option[R] In here first....... taking T => R & returning Option[T] => Option[R] 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 50 |
package com.myapp object Test2 extends App { trait Wrapper[C[_]] { def aFunction[T,R](f: T => R): C[T] => C[R] } implicit object ListWrapper extends Wrapper[List] { //returns a function: so this is a higher order function override def aFunction[T,R](f: T => R): List[T] => List[R] = { l => println("taking T => R & returning List[T] => List[R]") l.map(f) } } implicit object OptionWrapper extends Wrapper[Option] { //returns a function: so this is a higher order function override def aFunction[T,R](f: T => R): Option[T] => Option[R] = { l => println("taking T => R & returning Option[T] => Option[R]") l.map(f) } } // A function that returns an anonymous Object // Anonymous class Test2$$anon$1 AnyRef is like java.lang.Object private implicit def fnGenAnonymousObj[C[_], T](input: C[T]) = new { // or new AnyRef or new Object //takes a function as an arg: So, this is a higher order function def anotherFunction[R](f: T => R)(implicit wrap: Wrapper[C]): C[R] = { println("In here first.......") val aClosure: C[T] => C[R] = wrap.aFunction(f) // C[T] => C[R] val result: C[R] = aClosure(input) // pass the list to the closure 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 45 & 47 where new functions were added to handle Some(x) and None respectively via functions. Lines 39 to 42 make use of these functions.
Output:
1 2 3 4 5 6 7 8 9 10 |
In here first....... taking T => R & returning Option[T] => Option[R] In here first....... taking T => R & returning Option[T] => Option[R] Some(8) In here first....... taking T => R & returning Option[T] => Option[R] None |