In this document, we will understand what partially applied functions are and how they can be re-used to create other functions. We will also look at how partially applied functions are different from partial functions. Last but not the least we will look at function currying.

Partially Applied Functions

What are Partially Applied Functions

  1. Partially applied functions means that while applying the function, we do not pass in arguments for all of the parameters defined by the function, but only for some of them, leaving the remaining ones blank.
  2. What we get back is a new function whose parameter list only contains those parameters from the original function that were left blank.
  3. When a partially applied function is called, the arguments that have not been specified have to be specified as _

Example 1

def add(a: Int, b: Int) = a + b
val onePlusFive: Int = add(1, 5) // 6
val addToFour: Int => Int = add(4, _) //Returns a function Int => Int
val addFourPlusTwo: Int = addToFour(2) // 6

Example 2: Creating a size filter on Strings

type Predicate = (Int, Int) => Boolean
def sizeConstraint(p: Predicate, n: Int, text: String) = p(text.size, n)
val gte: Predicate = _ >= _ // gte: Predicate = <function2>
val lte: Predicate = _ <= _ // lte: Predicate = <function2>
val minSize: (Int, String)=>Boolean = sizeConstraint(gte, _, _)
// minSize: (Int, String) => Boolean = <function2>

val maxSize: (Int, String)=>Boolean = sizeConstraint(lte, _, _)
// maxSize: (Int, String) => Boolean = <function2>
minSize(5, "John")  
// res0: Boolean = true

minSize(10, "John") 
// res1: Boolean = false

 

Example 3: Creating a generic function for sum/product on function applied to range of numbers.

e.g. sum of cubes, product of squares, factorial, etc

import scala.annotation.tailrec
def operate(f:Int=>Int, 
            combine:(Int,Int) => Int, 
            identity: Int, 
            start:Int, 
            end:Int):Int = {

  @tailrec
  def functionLoop(current:Int, accumulator:Int):Int =
    if(current > end) accumulator
    else functionLoop(current+1, combine(f(current), accumulator))

  functionLoop(start, identity)
}

Now we will demonstrate how the above operate function can be re-used to create other partially applied functions

// Returns a function that can calculate sum
// for a given range of integers after applying a function to each integer
// Returns a function (Int => Int, Int, Int) => Int
def sumF = operate(_:Int=>Int, (x:Int,y:Int) => x+y, 0, _:Int, _:Int)
// Returns a function that can calculate product
// for a given range of integers after applying a function to each integer
// Returns a function //(Int => Int, Int, Int) => Int
def prodF = operate(_:Int=>Int, (x:Int,y:Int) => x*y, 1, _:Int, _:Int)
// Returns a function that can calculate sum of cubes, for a range of integers
// Returns a function (Int, Int) => Int
def sumCubes = sumF(x=>x*x*x, _:Int, _:Int )
// Returns a function that can calculate product of squares, for a range of integers
// Returns a function (Int, Int) => Int
def prodSquares = prodF(x => x*x, _:Int, _:Int)
// Returns a function that can be used to calculate factorial of a number
// Returns a function Int => Int
def factorial = prodF(x=>x, 1, _:Int)

So as we can see, we re-used the same function ‘operate‘ to create other functions by passing only some of the parameters. These new functions can now be used in the following way. (The consumer of these functions don’t know anything about the original ‘operate‘ function, neither they have to pass all the 5 parameters that operate require)

sumCubes(1, 5)  // 225

prodSquares(2,4)  // 576

factorial(5)  //120

 

How is Partially Applied functions different from Partial Functions?

They are different from Partial functions in a way that partial functions are unary function that does not support every possible value that meets the input type.

However partial functions need to be passed all the parameters, whereas partially applied functions can be passed some of the parameters.

 

Currying

Currying means taking a function that takes multiple arguments and turning it into a chain of functions each taking one argument and returning the next function, until the last returns the result.

In pseudocode, this means that an expression like this

result = f(x)(y)(z)

Above function is mathematically same as something like this:

f1 = f(x)
f2 = f1(y) 
result = f2(z)

 

Transformation of a normal function (that takes multiple arguments) to a curried function (that takes a chain of functions, each taking single argument), can be done using the curried method on functions.

Example 1: Creating curried functions from functions with multiple arguments.

Below are two addition functions, that take multiple arguments

val sum: (Int,Int)=>Int = _ + _
def add(x: Int, y: Int): Int = x + y

Creating a curried function automatically

def sumCurried: Int => (Int => Int) = sum.curried

Creating a curried function manually

def addCurried(x: Int)(y: Int): Int = x + y

Using curried functions, and normal functions both will yield same results

sum(1,5)  // Result is 6
add(1,5)  //Result is 6
sumCurried(1)(5) //Result is 6
addCurried(1)(5)  //Result is 6

 

Example 2

Below is the example of a curried function that can be used to create sum of numbers, product of numbers, factorial, sum of cubes, etc

import scala.annotation.tailrec
def functionOp(f:Int=>Int)(combine:(Int,Int) => Int)(identity: Int)(start:Int)(end:Int):Int = {
  @tailrec
  def functionLoop(current:Int, accumulator:Int):Int =
    if(current > end) accumulator
    else functionLoop(current+1, combine(f(current), accumulator))

  functionLoop(start, identity)
}

 

Passing just the first argument to above function, will create another curried function

def id = functionOp(x => x)_
//((Int, Int) => Int) => (Int => (Int => (Int => Int)))
def product = id((x,y) => x*y)(1)
//Int => (Int => Int)
def factorial(n:Int):Int = product(1)(n)
//Int => Int
def sumSquares = functionOp(x=>x*x) ((x,y) => x+y) (0)_
//Int => (Int => Int)
def prodSquares = functionOp(x=>x*x) ((x,y) => x*y) (1)_
//Int => (Int => Int)

Above functions can be used in following way

factorial(5)  // 120
factorial(10) //3628800
sumSquares(1)(5)  //55
prodSquares(1)(5) //14400

 

Example 3

Below is a curried function to check if  mod(x,n) is true

def modN(n: Int)(x: Int):Boolean = ((x % n) == 0)

Above curried function can now be used to filter the list to get all elements that are divisible by 2, or all numbers divisible by 3

val nums = List(1, 2, 3, 4, 5, 6, 7, 8)

nums filter modN(2) 
//Filter list to get all numbers divisible by 2 =  List(2, 4, 6, 8)

nums filter modN(3) 
//Filter list to get all numbers divisible by 3 = List(3, 6)

 

 

Reference

  1. https://www.coursera.org/learn/progfun1/home/welcome
  2. http://danielwestheide.com/blog/2013/01/30/the-neophytes-guide-to-scala-part-11-currying-and-partially-applied-functions.html