Partially Applied Functions
Think about functions and ask this question to yourself.
What is a function?
Whatever definition you accept, you can always break a function into two parts.
- A fixed part (function body)
- A variable part (function arguments)
We keep the fixed part into the body of a function and pull the variable parts outside the body as a parameter list. The purpose is to create a generalized solution. Let's look at a couple of simple examples.
Above functions are generic solutions for a pair of numbers. The first one is a
division and the second one is an addition.
These are small and simple functions, but they also have a fixed part (function
body)
and a variable part (argument list). Have you ever thought that fixing some of the
variable
parts (arguments) can give you an entirely different solution?
Let's see an examples.
The above code is an inverse function. The division is a general solution, and the
inverse is a particular case of a division
operation. Isn't it? You fix the first variable to one, and the division becomes an
Inverse
operation. Similarly, you set the first variable to one, and the addition becomes
an
increment operation. Inverse and increment are entirely new solutions.
Let's say. You have a function to charge a customer for an online purchase. It
takes
two parameters. The payment method and the amount. You fix the payment method to
the
credit card, and it becomes a new function. PayByCreditCard. Similarly, you can
create
other specialized cases like PayByWallet. The idea is similar to the one shown in
below
figure.
Take a generic function, fix the value for some parameters and you get a brand new specialized function. We have a standard terminology for this whole idea. That is what we call the partially applied functions. The notion of partially applied function is powerful, and hence Scala offers a syntax to implement the partial application of parameters. The implementation is extremely simple.
Partially Applied Function Syntax
Let me take the division function and use it to demonstrate the syntax.
The first line in the above code is my division function. I said, fix the first
parameter to 1, and you can create a brand
new function for the inverse. The second line does the same thing. We fixed the
first
parameter to one and gave a placeholder for the second parameter.
In fact, we applied the div function to its first argument and left the second
argument
as unapplied. That's why we call it a partially applied function.
We have a small limitation in this syntax. We must specify the data type for
the
unapplied parameters. The type inference doesn't work in this case.
If you look at the output of the partially applied function, Scala returned a
new
function. Strange. Isn't it. If I look at the implementation of my div function, It
doesn't
return a function. It simply returns a double. But in case of partially applied
functions,
the Scala compiler will do a couple of things internally, and instead of returning
a
double, it will return a new function that takes the remaining unapplied
parameters.
You can hold the returned function into a val. That's what we do in the next
line.
The inverse is now a new function that takes a single argument because we
didn't
apply one argument earlier. You can call inverse as shown at the last line.
Amazing.
Isn't it.
Partial application in Higer Order functions
In functional programming languages, the notion of partially applied function is
much more powerful because we can have a
parameter that itself is a piece of code (a function literal). You can achieve much
more
by hooking up a new algorithm to a generalized Higher Order Function.
Let's see an example. I have got a requirement to calculate different types of
sums
for given bounds. Let's assume the lower bound is 1 and the upper bound is 5. And
the
requirement is to calculate three types of sums as listed below.
- A simple sum.
1 + 2 + 3 + 4 + 5 = 15 - A sum of squares.
1*1 + 2*2 + 3*3 + 4*4 + 5*5 = 55 - A sum of cubes.
1*1*1 + 2*2*2 + 3*3*3 + 4*4*4 + 5*5*5 = 255
Do you want to create three different functions for these three requirements? I don't want to do that because I am expecting to get some more similar requirements. So I decided to create a higher order function. The code is shown below.
The first parameter is the logic. The other two parameters are the lower and upper bounds. I can fulfill all the three requirements using the higher-order function SumOfX. The code is shown below.
To calculate a simple sum, I can use an identity function. To calculate a sum of
squares, I can use a squaring function.
Similarly, to calculate a sum of cubes, I can use a cube function. I can also
fulfill
some future requirements. For example, to calculate a sum of factorials, all I need
to
do is to pass a factorial function.
Great. However, the example shows a use of a Higher Order function. There is
nothing
to do here with partially applied functions. Now, you might be wondering, why do we
need
a partially applied function? I mean, it's a nice idea. Take a generic function,
fix
some of the parameters and create a brand new function. But why the hell I need to
do
that? If at all I need to calculate a sum of cubes, I will simply use the
SumOfX
as shown above. Why do I take the pain to create a partially applied function and
then use it to calculate the sum of cubes?
There is a difference in the type. Let me explain. Let's create a partially
applied
function.
Now the sumOfCubes is a function that takes two parameters. But sumOfX was taking three parameters. When you have a downstream function that expects sumOfCubes, you cannot use sumOfX. Let's take an example.
The applySums takes a function of type (Int, Int) => Int. If you try passing sumOfX, you will get a type mismatch error. However, you can use a partially applied function to generate a function of type (Int, Int) => Int. Let's try it.
So, the partially applied function is a technique to generate a brand new function.
And the generated function comes with
a brand new type. You mostly need it to pass it to the downstream functions.
Continue reading for more on Scala functions.
Read More
Basics of Scala functions | Function Literals in Scala | Function values | Local Functions | Variable length argument | Default values and named arguments | Scala Placeholder syntax | Higher Order functions | Partially applied functions | Function currying