Suppose you work in a restaurant as a chef and one of your colleagues ask you a question: Implement a HOF (higher-order function) that performs currying. Looking for clues? Suppose you have the following two signatures for your HOF:
def curry[X,Y,Z](f:(X,Y) => Z) : X => Y => Z
Similarly, implement a function that performs uncurrying as follows:
def uncurry[X,Y,Z](f:X => Y => Z): (X,Y) => Z
Now, how could you use HOFs to perform the currying operation? Well, you could create a trait that encapsulates the signatures of two HOFs (that is, curry and uncurry) as follows:
trait Curry {
def curry[A, B, C](f: (A, B) => C): A => B => C
def uncurry[A, B, C](f: A => B => C): (A, B) => C
}
Now, you can implement and extend this trait as an object as follows:
object CurryImplement extends Curry {
def uncurry[X, Y, Z](f: X => Y => Z): (X, Y) => Z = { (a: X, b: Y) => f(a)(b) }
def curry[X, Y, Z](f: (X, Y) => Z): X => Y => Z = { (a: X) => { (b: Y) => f(a, b) } }
}
Here I have implemented the uncurry first since it's easier. The two curly braces after the equals sign are an anonymous function literal for taking two arguments (that is, a and b of types X and Y respectively). Then, these two arguments can be used in a function that also returns a function. Then, it passes the second argument to the returned function. Finally, it returns the value of the second function. The second function literal takes one argument and returns a new function, that is, curry(). Eventually, it returns a function when called returns another function.
Now it comes: how to use the preceding object that extends the base trait in a real-life implementation. Here's an example:
object CurryingHigherOrderFunction {
def main(args: Array[String]): Unit = {
def add(x: Int, y: Long): Double = x.toDouble + y
val addSpicy = CurryImplement.curry(add)
println(addSpicy(3)(1L)) // prints "4.0"
val increment = addSpicy(2)
println(increment(1L)) // prints "3.0"
val unspicedAdd = CurryImplement.uncurry(addSpicy)
println(unspicedAdd(1, 6L)) // prints "7.0"
}
}
In the preceding object and inside the main method:
- The addSpicy holds a function that takes a long as a type and adds 1 to it and then prints 4.0.
- The increment holds a function which takes a long as a type and adds 2 to it and finally prints 3.0.
- The unspicedAdd holds a function which adds 1 and takes a long as type. Finally, it prints 7.0.
The output of the preceding code is as follows:
4.0
3.0
7.0
Currying: Currying is useful in both practical and theoretical settings. In functional programming languages, and many others, it provides a way of automatically managing how arguments are passed to functions and exceptions. In theoretical computer science, it provides a way to study functions with multiple arguments in simpler theoretical models, which provide only one argument.
Uncurrying: Uncurrying is the dual transformation to currying, and can be seen as a form of defunctionalization. It takes a function f whose return value is another function g and yields a new function f′ that takes as parameters the arguments for both f and g, and returns, as a result, the application of f and subsequently, g, to those arguments. The process can be iterated.
So far, we have seen how to deal with pure, higher-order, and anonymous functions in Scala. Now, let's have a brief overview on how to extend the higher-order function using Throw, Try, Either, and Future in the following section.