top of page
Search

Partially Applied Functions And Currying

Written by Nihal Kashyap


Scope


  • The article explains partially applied functions and a special version of it, known as currying.

  • It stresses on how we can convert ordinary functions to specialised and reusable functions.

  • The article further explains how the reusable functions can be used to compose new functions.


Partially Applied Functions


Partially applied functions are functions that don't take in all the parameters. Simply put the function is applied only to a subset of the parameters. Such functions always return a function that takes in the rest of the parameters.


Here, we define a function sumPaf that takes in an integer parameter and returns a function that takes in two integer parameters and return an integer.


val sumPaf: Function1[Int, Function2[Int, Int, Int]] = new Function1[Int, Function2[Int, Int, Int]] {
override def apply(n1: Int) = new Function2[Int, Int, Int] {
override def apply(n2: Int, n3: Int) = n1 + n2 + n3
}
}

Scala provides a concise way of defining functions:


val sumPaf: Int => (Int, Int) => Int = n1 => (n2, n3) => n1 + n2 + n3

val partialSum: (Int, Int) => Int = sumPaf(2)

val sum: Int = partialSum(3, 5)

println(sum) // 10

Or, we can define a method and convert it into a partially applied function using ETA expansion or lifting.


def sumPaf(n1: Int, n2: Int, n3: Int) = n1 + n2 + n3

val partialSum: (Int, Int) => Int = sumPaf(2, , ) // ETA expansion or lifting

val sum: Int = partialSum(3, 5)

println(sum) // 10

Currying


Named after the mathematician Haskell Curry,  currying is the concept of converting a function that takes in two or more parameters into a function that takes in a sequence of single parameter.


Let's try to curry the sumPaf function.


val sumPaf: Int => Int => Int => Int = n1 => n2 => n3 => n1 + n2 + n3

val sum: Int = sumPaf(2)(3)(5)

println(sum) // 10

Currying is a special version of the partially applied function. Not all partially applied functions are curried functions but all curried functions are partially applied function.


A brief about function composition


Before diving deeper, let's understand what function composition is.


Function composition is assembling functions together to build new functions.  We can chain a sequence of simple functions so that the output of one function is fed as an input to another function.


f(x) =  x + 2

g(x) = x * 5

h(x) =  g(f(x))


h(2)  = 20 // f(2) = 4 and then g(4) = 20


The input x is applied to g first and then the output is fed as the input to the function f.

Function composition is similar to chaining Unix commands using pipes.


It can be implemented in Scala by using the andThen and compose methods.

Note:  Both andThen and compose methods work only with functions not methods.


val f: Int => Int = x => x + 2
val g: Int => Int = x => x * 5

val h: Int => Int = f andThen g
println(h(2)) // 20

val m: Int => Int = g compose f
println(m(2)) // 20

The difference between andThen and compose is andThen operates from left to right and compose operates from right to left.


What problem does it solve?


Partially applied functions and Curried functions can be used to create more specialised versions of a general function, by breaking it down into smaller and modular functions.  Partially applied functions facilitate code reusability and readability. The smaller and modular functions can be reused and composed with other functions to build new functions.


Let's understand their benefits by solving a more practical use case.


Use case:

We need to calculate the selling price of a set of products. A discount is applied on the product and then sales tax is applied on the discounted price to arrive at the final price.


Take the discount as 10 %. Take the tax rate as 5 %.


Solution:


Define the variables and helper methods:


val originalPrices: List[Double] = List(100, 200, 500)
val discount: Double = 10
val taxRate: Double = 5

def calculateDiscountedPrice(originalPrice: Double, discount: Double): Double = originalPrice (1 - discount / 100)

def calculateFinalPrice(discountedPrice: Double, taxRate: Double): Double = discountedPrice * (1 + taxRate / 100)

The naive approach:


originalPrices.map { originalPrice =>
val discountedPrice: Double = calculateDiscountedPrice(originalPrice, discount)
calculateFinalPrice(discountedPrice, taxRate)
}

The Functional approach:


val discountedPricePaf: Double => Double = calculateDiscountedPrice(_, discount)
val finalPricePaf: Double => Double = calculateFinalPrice(_, taxRate)
val finalPrice: Double => Double = discountedPricePaf andThen finalPricePaf
originalPrices.map(finalPrice)

Let's define another partially applied function that adds a markup to the original price.


val markUp: Double = 6
def calculateMarkUp(originalPrice: Double, markUp: Double): Double = originalPrice * (1 + markUp / 100)
val calculateMarkUpPaf: Double => Double = calculateMarkUp(_, markUp)

We can now reuse  the calculateMarkUpPaf with finalPricePaf to calculate the final price.


val finalPriceWithMarkUp: Double => Double = calculateMarkUpPaf andThen
finalPricePaf
originalPrices.map(finalPriceWithMarkUp)

Observations from the above example:


Compared to the naive approach the functional approach gives us the following advantages:


  • Modularity and Reusability: The functions discountedPricePaf and finalPricePaf are modular and can be reused as independent functions.

  • Flexibility:  The parameters taken in by the partially applied functions need not be readily available. Unlike in the naive approach where we need to pass both the originalPrice and discount together, in the functional approach, we first apply the parameter discount and the resultant function awaits the originalPrice. 

  • Composable: These functions can be chained or composed with other functions to build new functions.


Summary


  • Partially applied functions are functions where parameters are applied partially.

  • Curried functions are a special version of partially applied functions that take in a sequence of single parameter.

  • While all curried functions are partially applied functions, it is important to note that all partially applied functions may not be curried functions.

  • Both partially applied functions and curried functions enhance code reusability and readability.

  • Transforming a general function into a partially applied function results in modular and independent functions that could be chained or composed together to build new functions.


Conclusion


Partially applied functions and currying along with other functional programming concepts encourage writing modular, reusable and maintainable code. These concepts empower developers to tackle complex problems with elegance and precision, aligning with the principles of functional programming.



___


To submit a blog, e-mail Patrycja@umatr.io 📧



Commentaires


bottom of page