Stefan Compton
Posted on April 19, 2022
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]
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)
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 = ...
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 = ...
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
(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)
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 = ...
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]
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.
Posted on April 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.