Partial Application, Currying, Composition
In the same way, in the object oriented realm classes and objects are used as the basic building blocks of constructing a larger system of software, in the functional world functions are the basic building blocks. We will look at some of the basic patterns used for using functions as constituents of a composable software architecture.
Partial Application
Let us say, we write a function to find the product of two numbers as below.
scala> def times(x: Int, y: Int) = x * y times: (x: Int, y: Int)Int scala> times(2, 10) res0: Int = 20
Now, let us say we want to reuse the logic above to create functions, where the value of x is constant. You could write a function, that takes a function that takes two arguments and the value of one of its parameters and return a function that takes one argument, reapplying the logic of the initial function, with the value of the first argument fixed. In the functional world this is called partial application of the function.
scala> def times(x: Int, y: Int) = x * y times: (x: Int, y: Int)Int scala> def partial(x: Int, f: (Int, Int) => Int) = | (y: Int) => f(x, y) partial: (x: Int, f: (Int, Int) => Int)Int => Int scala> val times2 = partial(2, times) times2: Int => Int = <function1> scala> times2(10) res0: Int = 20
If we want to generalise the functional partial, we can use type parameters and write,
scala> def partial[X, Y, Z](x: X, f: (X, Y) => Z): Y => Z = | (y) => f(x, y) partial: [X, Y, Z](x: X, f: (X, Y) => Z)Y => Z
Since this is a pattern that recur often enough, Scala provides a syntactic sugar to apply the construct as below, where the second parameter can be omitted out as below.
scala> val times2 = times(2, _: Int) times2: Int => Int = <function1> scala> times2(10) res1: Int = 20
The missing parameter is indicated with an underscore. It is a bit strange that we need to specify the type parameter explicitly, but that is an intricacy left to type inference in the language specification.
Currying
Another similar pattern is currying, named after Haskell Curry, where a function that takes n parameters is transformed to n functions that take one parameter each. This pattern can also be used for addressing the functional composition problem addressed in the last section as below,
scala> def curry[X, Y, Z](f: (X, Y) => Z): X => Y => Z = | (x) => (y) => f(x, y) curry: [X, Y, Z](f: (X, Y) => Z)X => (Y => Z) scala> val curried = curry(times) curried: Int => (Int => Int) = <function1> scala> val times2 = curried(2) times2: Int => Int = <function1> scala> times2(10) res2: Int = 20
Again this is a pattern the recur so often, Scala provides a syntactic sugar to write a function that takes a list of n parameters, as a function that takes n set of one parameter each. Based on that, the times function can be written as below,
scala> def times(x: Int)(y: Int) = x * y times: (x: Int)(y: Int)Int scala> val times2 = times(2) _ times2: Int => Int = <function1> scala> times2(10) res3: Int = 20
Composition
Another common pattern that recurs frequently is the need of chaining two functions, where the output of one function is passed to the input of another as shown below,
scala> def chain[X, Y, Z](a: X => Y, b: Y => Z): X => Z = | (x) => b(a(x)) chain: [X, Y, Z](a: X => Y, b: Y => Z)X => Z scala> def reverse(s: String) = s.reverse reverse: (s: String)String scala> def trim(s: String) = s.trim trim: (s: String)String scala> val reverseAndTrim = chain(reverse, trim) reverseAndTrim: String => String = <function1> scala> reverseAndTrim(" Hello World ") res5: String = dlroW olleH
The Scala core library provides a function to do the above out of box. The call compose is on the trait Function1, which represents any function that takes one argument.
scala> val reverseAndTrim = reverse _ compose trim _ reverseAndTrim: String => String = <function1> scala> reverseAndTrim(" Hello World ") res9: String = dlroW olleH
Good point. I hadn’t thohgut about it quite that way. 🙂