Eta Expansion in Scala

stefintrim

Stefan Compton

Posted on April 19, 2022

Eta Expansion in Scala

Eta Expansion

"Can you tell me about Eta Expansion?"

I froze. My pulse raised, temperature started rising, throat dried. I mumbled something about methods and functions and how Scala is OO. But that I didn't know, sorry! Please stop staring.

This was my reaction to a fairly innoccuous question in an interview for a Scala contract. The work never came off. The company apparently wanted to make an offer, alas internal politics interviened. But I can't help thinking it was my lack of a coherent answer to that question that led to my downfall!

Incidentally this is not an untypical reaction that I have in interview situations. Fortunately coding has nothing to do with the instant recall of textbook process descriptions and everything to do with solving problems, but there's not really a good way to test that in a 30 minute interview. So here we are.

This blog is my attempt to put down an actual answer to that question, and in doing so gain some extra insight into Scala and also FP in general.

So what is Eta Expansion?

One of the core tenets of FP is that functions are first-class citizens. If you can do something with a value, then you can do it with a function. Consider the definition of the method map on the List class

final def map[B](f: (A) => B): List[B]
Enter fullscreen mode Exit fullscreen mode

We can see that map on a List[A] takes a function as an argument that will map elements from a type A to type B, and return a List[B]

So we can turn numbers into strings like so

List(1, 2, 3).map(n => n.toString)
Enter fullscreen mode Exit fullscreen mode

So far, so functional. Our anonymous function satisfies the signature of map, which takes a function A => B (or Int => String in this case).

But what if we want to pass in a method. We know that scala is OO and so when we say

def f(n: Int): String = ...
Enter fullscreen mode Exit fullscreen mode

then f is a method on the class we're working in, and not a function. We would define a function as

val f: (Int) => String = ...
Enter fullscreen mode Exit fullscreen mode

And (in Scala 2.x) we can put a function in a List (first class, remember), but not a method

val fFunction: (Int) => String = ...
def fMethod(n: Int): String = ...
List(fFunction) // ok
List(fMethod)   // not ok
Enter fullscreen mode Exit fullscreen mode

(This is not the case in Scala 3, which has improved Eta Expansion to allow this and also add in support of auto-expanding curried functions)

But hang on a sec, my Scala compiler will cheerfully allow

def f(n: Int): String = 
....
List(1, 2, 3).map(f)
Enter fullscreen mode Exit fullscreen mode

And this is where Eta Expansion comes in. Phew! That's a long way to go without touching how it works.

It's worth noting that when we define a function like

val f: (Int) => String = ...
Enter fullscreen mode Exit fullscreen mode

This is syntactic sugar and what's really under the hood is an object which implements the trait FunctionN, or specifically in this case

Function1[Int, String]
Enter fullscreen mode Exit fullscreen mode

And it's associated apply method, which will apply the function to given argument(s).

So there we have it, Eta Expansion somewhat expanded upon. There's lots more to unpack, especially in Scala 3, so I may come back to this in the future.

💖 💪 🙅 🚩
stefintrim
Stefan Compton

Posted on April 19, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related