Scala Foundation Course - Partially Applied Functions


In this video, I am going to talk about two things.

  1. Partially applied functions
  2. 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.

                                    
    def div(x:Double, y:Double):Double = x/y
    def add(x:Double, y:Double):Double = x+y                                        
                             

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.

                                        
    def inv(y:Double):Double = 1.0/y                                        
                                 

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.

                                            
    def inc(y:Double):Double = 1 + y                                         
                                     

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.

                                                
    def div(x:Double, y:Double):Double = x/y                                        
                                         

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.

                                                    
    div(1, _:Double)                                       
                                             

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.

                                                        
    val inv = div(1, _:Double)                                      
                                                 

Now, the inverse function takes a single argument because we didn't apply one argument earlier. You can call inverse like this.

                                                            
    inv(10)                                     
                                                     

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.

  1. A simple sum like this 1 + 2 + 3 + 4 + 5 = 15
  2. A sum of squares like this 1*1 + 2*2 + 3*3 + 4*4 + 5*5 = 55
  3. 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.

                                                                
    def sumOfX(f:Int => Int, a:Int, b:Int):Int = 
    if (a>b) 0 else f(a) + sumOfX(f, a+1, b)                                        
                                                         

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.

                                                                    
    sumOfX(x=>x, 1,5)                                        
                                                             

To calculate a sum of squares, I can use a squaring function.

                                                                        
    sumOfX(x=>x*x, 1,5)                                        
                                                                 

Similarly, to calculate a sum of cubes, I can use a cube function.

                                                                            
    sumOfX(x=>x*x*x, 1,5)                                        
                                                                     

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.

                                                                                
    val sumOfCubes = sumOfX(x=>x*x*x, _:Int,_:Int)
    sumOfCubes(1,5)                                     
                                                                         

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.

                                                                                    
    def applySums(f: (Int, Int) => Int) = 
    println("I am applying sum of 1 to 5 and the answer is " + f(1,5))                                  
                                                                             

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.

                                                                                        
    def sum(x:Int, y:Int, z:Int) = x+y+z
    val s3=sum(_:Int, _:Int, _:Int)                                     
                                                                    

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.

                                                                                        
    val s4 = sum _                                     
                                                                                 

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.

                                                                                        
    def sumOfX(f:Int => Int, a:Int, b:Int):Int = 
    if (a>b) 0 else f(a) + sumOfX(f, a+1, b)                                        
                                                                                 

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.

                                                                                        
    def sumOfX(f:Int => Int)(a:Int, b:Int):Int = 
    if (a>b) 0 else f(a) + sumOfX(f, a+1, b)                                    
                                                                                 

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.

                                                                                        
    sumOfX(x=>x*x*x)(1,5)                                        
                                                                                 

This syntax is a bit more readable and stylish. Isn't it. Further, you can create a partially applied function like this.

                                                                                        
    val si = sumOfX(x=>x*x*x)_                                        
                                                                                 

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.

                                                                                        
    val si = sumOfX{
        x=>println("Cubing")
        x*x*x
    }_                                      
                                                                                 

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.


You will also like:


Kafka Core Concepts

Learn Apache Kafka core concepts and build a solid foundation on Apache Kafka.

Learning Journal

Pattern Matching

Scala takes the credit to bring pattern matching to the center.

Learning Journal

Apache Spark Introduction

What is Apache Spark and how it works? Learn Spark Architecture.

Learning Journal

Scala placeholder syntax

What is a scala placeholder syntax and why do we need it? Learn from experts.

Learning Journal

Higher Order functions

Scala allows you to create Higher Order functions as first class citizens.

Learning Journal