Scala functions and currying
What is currying? From Wikipedia currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions. Let’s not dive into lambda calculus, but look at it from a developer’s perspective. How does it help in real live?
Haskell
let f1 = \x y z -> x + y + z
-- f1 :: Integer -> Integer -> Integer -> Integer
let f2 = \x -> \y -> \z -> x + y + z
-- f2 :: Integer -> Integer -> Integer -> Integer
As we can see from types these two definitions are equivalent. All arguments are treated equally and distinguished only by their position. Given proper order it allows to create elegant transformations:
We used currying twice here: (2 +)
creates a single argument function which adds 2
to it’s argument, then map (2 +)
creates a single argument function which takes a list and applies previously defined function to every element of it. Nice and clean.
Scala
To start with, Scala doesn’t have Haskell-type functions. In fact, every functional object in Scala is a method. What’s the difference? Scala is OO language, so it works with objects. Objects consist of a state and methods, so every method is attached to some object. What does it change? Well, every method has an additional implicit argument, called this
. So when we write 2 + 3
we in fact call some polymorphic function @+(2, 3)
, which in OO notation looks like 2.+(3)
. As a result not all arguments are created equal and currying becomes much less elegant. Let’s write (2 +)
in Scala:
Looks almost like Haskell, right? Wrong! What is happening here is called eta-expansion, which converts method to a function. That’s the reason why we need to specify type here. And by the way, function in Scala is an object which defines apply()
. So compiler effectively created a transformation of one object into another, which is roughly equivalent to:
But similarities with Haskell stop here. Next step is to write map plus_2
and we are stuck. Since any method is attached to an object, map
belongs to something. It can’t belong to plus_2
since plus_2
is just a generic <function1>
. So it belongs to an instance of e.g. List
(i.e. the first argument is always a List
). But I’m not interested in creating generic transformation for a specific instance of a List
. I can always define a partial function, but the simplicity is lost.
To curry a method with more than one parameter we can use curried
, but curried
only applies to a <functionN>
and eta-expansion is not applicable in this context, so we have to use _
:
What is interesting in the above example is Any
, it’s presumably there because there are two versions of indexOf
, with one and two arguments, but curried
in fact uses the second one because the first parameter has the same type in both and it’s not possible to distinguish between them by analyzing the first argument.
Okay, that hasn’t worked well so far. What about multiple parameter lists? They are what is usually called currying in Scala. They don’t pass a basic test though, multiple parameter lists mean exactly that, lists. A list can have a size of 0, 1, 2 …
def x1(a: Int)(b: Int) = 1
// x1: (a: Int)(b: Int)Int
def x2()(b: Int) = 1
// x2: ()(b: Int)Int
def x3(a: Int)() = 1
// x3: (a: Int)()Int
def x4(a: Int, b: Int)(c: Int) = 1
// x4: (a: Int, b: Int)(c: Int)Int
Let’s define a method with one argument in every list and call it currying. Nay, this doesn’t work either:
def x1(a: Int)(b: Int) = 1
x1(2)
// missing arguments for method x1;
// follow this method with `_' ...
So what this “currying” is good for? Well, it’s a type inference tool. Consider this:
Scala has a local inference, so if I put all parameters into the same list it won’t infer function type in this example:
List(1,2,3).myFoldLeft(0, _ + _)
// Error, can't infer the type of f
List(1,2,3).foldLeft(0)(_ + _)
// No problem here
The reason why the second example works is because Scala checks the first parameter list first and infers that B
is Int
. So finding type of f
becomes simple. In the first case it’s not possible.