In this video, I am going to talk about two things.
- Partially applied functions
- Function currying
Let's start with partially applied functions.
Partially applied functions in Scala
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
and a variable part.
We keep the fixed part inside 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.
These two 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 and a variable part.
Have you ever thought that fixing some of the variable parts can give you an entirely
different
solution?
Let me show you.
Here 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 addition becomes an increment operation.
Inverse and increment are entirely new solutions. Isn't it?
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.
So, the idea is this.
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. We call it the partially applied
function.
The Idea is powerful, and hence Scala offers a syntax to implement the partially applied
functions.
The implementation is extremely simple. Let me take the division function and use it to
demonstrate
the syntax.
Here is my division function.
I said, fix the first parameter to 1, and you can create a brand-new function for the inverse.
Let's do that.
Fix the first parameter to one. Give a placeholder for the second parameter.
What are we doing? We are applying the div function to its first argument. We leave the second
argument as unapplied. That's
why the name partially applied.
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.
Okay. So, if you call this partially applied function, Scala will return a new function.
Strange.
Right?
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.
Now, the inverse function takes a single argument because we didn't apply one argument earlier. You can call inverse like this.
Amazing. Isn't it.
Partially applied Higher Order function
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. I mean a Higher-order function. 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 a given bound. Let's
assume
the lower bound is 1 and the upper bound is 5. And the requirement is to calculate three types
of
sums.
- A simple sum like this 1 + 2 + 3 + 4 + 5 = 15
- A sum of squares like this 1*1 + 2*2 + 3*3 + 4*4 + 5*5 = 55
- A sum of cubes like this 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 first parameter is the logic. Other two parameters are lower and upper bounds. I can fulfil
all three requirements using
this higher-order functions.
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 fulfil some future requirements. For example, to calculate a sum of factorials, all
I need to do is to pass a
factorial function.
Great. That's 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 like above. Why do I take the pain to create a partially applied function and
then
use it to calculate the sum of cubes?
Pause here for a minute and think about it.
There is a difference in the type. Let me show you. Let's create a partially applied
function.
Now the sumOfCubes is a function that takes two parameters. But sumOfX is a function that takes three parameters. When you have a downstream function that expects sumOfCubes, you can use sumOfX. For example, this.
It takes a function of
(Int, Int) => Int type. If you try passing
sumOfX, you will get a type mismatch error. But you can use a partially applied
function
to generate a function of that type.
So, the partially applied function is a technique to generate a brand-new function. And the
generated
function comes with a brand-new list of parameters. You mostly need it to pass it to the
downstream
functions.
The Idea is simple. Create generic functions. Apply one or more parameters to create brand
new
specialized functions. The syntactical rules for partially applied functions are
straightforward.
Use the underscore as a placeholder for unapplied arguments. You must remember to specify data
types.
The type inference doesn't help here.
There is one more minute detail of partially applied functions. Let me cover that as well.
You can also skip all the arguments. Let me show you an example.
In this example, I am skipping all the arguments. The sum and s3 are same. Isn't it? But there is a small difference. The sum is a function definition, and s3 is a function value. This example is a technique to convert a function definition to a function value. We convert a function definition to a function value quite often, so Scala offers a shortcut for skipping all the arguments.
A single underscore without parenthesis is a placeholder for the entire parameter list. So s4
and s3 are the same things.
That's all about partially applied functions.
Function Currying in Scala
There is another interesting concept that we often associate with the partially applied
functions. The function currying.
Let me explain.
Let's come back to the definition of
sumOfX.
When I already know that I am going to use the sumOfX to create multiple partially applied functions. Instead of using the above syntax, I will use parameter grouping.
I have broken the parameter list into two groups. The first group takes the function literal. The second group takes the lower and upper bounds. This makes the function definition more readable and easy to follow. It also impacts the way we call the function. Now, we use sumOfX like this.
This syntax is a bit more readable and stylish. Isn't it. Further, you can create a partially applied function like this.
If you remember the earlier syntax, the curried syntax looks cleaner. Isn't it?
If the first parameter group is a multiline function literal, the Currying syntax looks
more
convenient because you can change the parenthesis to a pair of curly braces.
This coding style looks cleaner. Isn't it?
So, function currying in Scala is nothing but syntactic sugar. The function currying allows
you
to split your function parameters into multiple groups. Scala will internally generate a series
of
functions for each argument group. That's what we want in partially applied functions. So, we
often
use it with partially applied functions.
We can also use them with a Higher Order function to make a function call in a series. We
have
already seen an example in the earlier video. You can also use them at other places. I will
show
you some other interesting examples as we progress through this tutorial.
That's it. That's all about function currying in Scala.
Thank you very much for watching Learning Journal. Keep Learning and Keep Growing.