Fun with function composition in Scala
We’ll start with the list of numbers 1 to 5 and some simple functions — one for adding 1, another for squaring, and third for adding 100.
01 02 03 04 05 06 07 08 09 10 11 | scala> val foo = 1 to 5 toList foo : List[Int] = List( 1 , 2 , 3 , 4 , 5 ) scala> val add 1 = (x : Int) = > x + 1 add 1 : (Int) = > Int = <function 1 > scala> val add 100 = (x : Int) = > x + 100 add 100 : (Int) = > Int = <function 1 > scala> val sq = (x : Int) = > x * x sq : (Int) = > Int = <function 1 > |
We can then apply any of these functions to each element in the list foo by using the map function.
1 2 3 4 5 6 7 8 | scala> foo map add 1 res 0 : List[Int] = List( 2 , 3 , 4 , 5 , 6 ) scala> foo map add 100 res 1 : List[Int] = List( 101 , 102 , 103 , 104 , 105 ) scala> foo map sq res 2 : List[Int] = List( 1 , 4 , 9 , 16 , 25 ) |
We can save the results of mapping all the values through add1, and then map the resulting list through sq.
1 2 3 4 5 | scala> val bar = foo map add 1 bar : List[Int] = List( 2 , 3 , 4 , 5 , 6 ) scala> bar map sq res 3 : List[Int] = List( 4 , 9 , 16 , 25 , 36 ) |
Or, if we don’t care about the intermediate result, we can just keep on mapping, through both functions.
1 2 | scala> foo map add 1 map sq res 4 : List[Int] = List( 4 , 9 , 16 , 25 , 36 ) |
What we just did, above, was sq(add1(x)) for every x in the list foo. We could have instead composed the two functions, since sq(add1(x)) = sq?add1(x). Here’s what it looks like in Scala:
1 2 3 4 5 | scala> val sqComposeAdd 1 = sq compose add 1 sqComposeAdd 1 : (Int) = > Int = <function 1 > scala> foo map sqComposeAdd 1 res 5 : List[Int] = List( 4 , 9 , 16 , 25 , 36 ) |
Of course, we can do this with more than two functions.
1 2 3 4 5 | scala> foo map add 1 map sq map add 100 res 6 : List[Int] = List( 104 , 109 , 116 , 125 , 136 ) scala> foo map (add 100 compose sq compose add 1 ) res 7 : List[Int] = List( 104 , 109 , 116 , 125 , 136 ) |
And so on. Now, imagine that you want the user of a program you’ve written to be able to select the functions they want to apply to a list of items, perhaps from a set of predefined functions you’ve provided plus perhaps ones they are themselves defining. So, here’s the really useful part: we can compose that arbitrary bunch of functions on the fly to turn them into a single function, without having to write out “compose … compose … compose…” or “map … map … map …” We do this by building up a list of the functions (in the order we want to apply them to the values) and then reducing them using the compose function. Equivalent to what we had above:
1 2 3 4 5 | scala> val fncs = List(add 1 , sq, add 100 ) fncs : List[(Int) = > Int] = List(<function 1 >, <function 1 >, <function 1 >) scala> foo map ( fncs.reverse reduce ( _ compose _ ) ) res 8 : List[Int] = List( 104 , 109 , 116 , 125 , 136 ) |
Notice the that it was necessary to reverse the list in order for the composition to be ordered correctly. If you don’t feel like doing that, you can use andThen in Scala.
1 2 | scala> foo map (add 1 andThen sq andThen add 100 ) res 9 : List[Int] = List( 104 , 109 , 116 , 125 , 136 ) |
Which we can of course use with reduce as well.
1 2 | scala> foo map ( fncs reduce ( _ andThen _ ) ) res 10 : List[Int] = List( 104 , 109 , 116 , 125 , 136 ) |
Since functions are first class citizens (something we used several times above), we can assign the composed or andThened result to a val and use it directly.
1 2 3 4 5 | scala> val superFunction = fncs reduce ( _ andThen _ ) superFunction : (Int) = > Int = <function 1 > scala> foo map superFunction res 11 : List[Int] = List( 104 , 109 , 116 , 125 , 136 ) |
This example is of course artificial, but the general pattern works nicely with much more complex/interesting functions and can provide a nice way of configuring a bunch of alternative functions for different use cases.
Reference: Fun with function composition in Scala from our JCG partner Jason Baldridge at the Bcomposes blog.
Related Articles :