Using higher-order functions

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
In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a a single argument. Currying is related to, but not the same as, partial application:
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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.119.136