Back2Basics: Do you know Scala Eta-Expansion and HOF Chemistry?

Table of contents
Reading Time: 2 minutes

I am working on Scala from last 2 years, and pretty confident about Scala concept like HOF, Currying and more. But Recently again looking into the HOF (Higher Order Functions) in Scala. I am pretty confident, HOF is “Passed a function as an argument”, “Assign functions to variables” and “Return functions from function” and the conclusion is Functions are First Class Citizen.

For me, these below statements are equal:-



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


val func = (a: Int) => a + 1
def funcd(a: Int) = a + 1
view raw

hof1.scala

hosted with ❤ by GitHub

First is, anonymous function or lambda expression and second is a function with the name. But we can pass these two function to any function which accepts the function as an argument with this signature Int => Int.

While I am trying to execute these two statements in scala REPL, but this time I noticed, the output of these two statements are different, which insist me to investigate why this behavior?



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> val func = (a: Int) => a + 1
func: Int => Int = $$Lambda$1030/1250816994@3d98d138
scala> def funcd(a: Int) = a + 1
funcd: (a: Int)Int
view raw

hof2.scala

hosted with ❤ by GitHub

The first line of code initializes some lambda expression but the second one defines as a function signature.

NOTE: In Scala 2.12.x Function[x] traits act as a Java 8 lambda expression rather than anonymous inner classes.  

After that, while I am looking into the Scala collection API `def map[B] (f: (A) ⇒ B)List[B]` method argument which looks like `val func = .. ` type but different from `def funcd … ` type.

So, next question is, while I am trying to execute code using func and funcd with List` map`  method, the code execution is successful???



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> List(1, 2, 3, 4, 5).map(func)
res0: List[Int] = List(2, 3, 4, 5, 6)
scala> List(1, 2, 3, 4, 5).map(funcd)
res1: List[Int] = List(2, 3, 4, 5, 6)
view raw

hof3.scala

hosted with ❤ by GitHub

But If we try to assign def funcd.. to the variable, We are getting this error.



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> val inc = funcd
<console>:12: error: missing argument list for method funcd
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `funcd _` or `funcd(_)` instead of `funcd`.
val inc = funcd
^
view raw

hof4.scala

hosted with ❤ by GitHub

After investigation the whole stuff, We are found the answer in one or two words called “Eta-Expansion“. This term itself is a broad term. But in the layman term compiler use Eta-Expansion for convert  `def func …` type to `lambda expression` or before Scala 2.12.x it converts into traitsFunction[x].

So, while we are trying to assign def func ... into the variable, we need to trigger Eta-Expansion manually which in Scala called Partially Applied Functions.



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> val inc = funcd _
inc: Int => Int = $$Lambda$1043/1332208607@56681eaf
view raw

hof5.scala

hosted with ❤ by GitHub

For Eta-Expansion, you can further read:

If you want to see the compiler translation, copy the whole bunch of code into scala file and compile using `$ scalac -Xprint:all Test.scala` command.



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


object Test extends App {
val func = (a: Int) => a + 1
def funcd(a: Int) = a + 1
val inc = funcd _
List(1, 2, 3, 4, 5).map(func)
List(1, 2, 3, 4, 5).map(funcd)
}
view raw

hof6.scala

hosted with ❤ by GitHub

These are thousands of lines are going to print, which I have no idea, what happens under the hood, but for us, the important part is Eta-Expansion which happens below:



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


/*
Translation 2:
def funcd(a: Int): Int = a.+(1);
private[this] val inc: Int => Int = {
((a: Int) => Test.this.funcd(a))
};
scala.collection.immutable.List.apply[Int](1, 2, 3, 4, 5).map[Int, List[Int]](Test.this.func)(immutable.this.List.canBuildFrom[Int]);
scala.collection.immutable.List.apply[Int](1, 2, 3, 4, 5).map[Int, List[Int]]({
((a: Int) => Test.this.funcd(a))
})(immutable.this.List.canBuildFrom[Int])
Translation 3:
private[this] val inc: Int => Int = {
{
final <artifact> def $anonfun$inc(a: Int): Int = Test.funcd(a);
((a: Int) => $anonfun$inc(a))
}
};
scala.collection.immutable.List.apply[Int](scala.Predef.wrapIntArray(Array[Int]{1, 2, 3, 4, 5})).map[Int, List[Int]](Test.this.func(), immutable.this.List.canBuildFrom[Int]());
scala.collection.immutable.List.apply[Int](scala.Predef.wrapIntArray(Array[Int]{1, 2, 3, 4, 5})).map[Int, List[Int]]({
{
final <artifact> def $anonfun$new(a: Int): Int = Test.funcd(a);
((a: Int) => $anonfun$new(a))
}
*/
view raw

hof7.scala

hosted with ❤ by GitHub

I am using translation as compiler steps, but not sure what we called. But according to above output, we can estimate somethings automatically happen in the case of the list as same as while we trigger Eta-Expansion manually.


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: Do you know Scala Eta-Expansion and HOF Chemistry?3 min read

Comments are closed.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading