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

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:-

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?

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???

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.

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.

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.

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:

/*
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.

%d bloggers like this: