/ EXERCISE, PROGRAMMING, STYLE

Composing Exercises in Programming Style

Last week saw us using higher-order functions by passing them as the parameter to another function, and dynamically calling them. The parameter passing is quite nice, but it’s not easy to follow the flow of the program.

This week we are going to keep those functions, but make use of them in a different way, with function composition.

This is the 6th post in the Exercises in Programming Style focus series.Other posts include:

  1. Introducing Exercises in Programming Style
  2. Exercises in Programming Style, stacking things up
  3. Exercises in Programming Style, Kwisatz Haderach-style
  4. Exercises in Programming Style, recursion
  5. Exercises in Programming Style with higher-order functions
  6. Composing Exercises in Programming Style (this post)
  7. Exercises in Programming Style, back to Object-Oriented Programming
  8. Exercises in Programming Style: maps are objects too
  9. Exercises in Programming Style: Event-Driven Programming
  10. Exercises in Programming Style and the Event Bus
  11. Reflecting over Exercises in Programming Style
  12. Exercises in Aspect-Oriented Programming Style
  13. Exercises in Programming Style: FP & I/O
  14. Exercises in Relational Database Style
  15. Exercises in Programming Style: spreadsheets
  16. Exercises in Concurrent Programming Style
  17. Exercises in Programming Style: sharing data among threads
  18. Exercises in Programming Style with Hazelcast
  19. Exercises in MapReduce Style
  20. Conclusion of Exercises in Programming Style

Function composition

Before you recoil in terror as it brings you bad memories from school, please read on the following: imagine a function f() that takes a X and returns a Y. Imagine another function g() that takes a type Y and returns a Z.

In Kotlin, this looks like the following:

fun f(x: X): Y = Y(x)
fun g(y: Y): Z = Z(y)

Now, let’s create a new function that calls each of them in turn, and pass the result of the first as the parameter to the second:

fun composed(x: X): Z = g(f(x))

This is more or less function composition. In math, this would be written as g o f.

Applying function composition to the exercise

To apply the above composition principle to the task at hand, we will design a method that "pipes" the output of a method to the input of another one. I deliberately used the word pipe, because this is the exact same principle used by the | operator in *Nix shells.

For example, those are the first 3 functions that can be defined:

fun readFile(filename: String) = read(filename)

fun filterChars(lines: List<String>) = lines
    .flatMap { it.split("\\W|_".toRegex()) }
    .filter { it.isNotBlank() && it.length >= 2 }

fun normalize(lines: List<String>) = lines.map(String::toLowerCase)

To store each step’s result, we create a class with:

  • a value placeholder
  • and the required pipe function - called bind() in the Python sample:
class TheOne(private var value: Any) {
    fun <T, V : Any> bind(function: (T) -> V) = apply {
        value = function(value as T)
    }
}

At this point, the class can be called like that:

TheOne(filename)                  (1)
    .bind(::readFile)             (2)
    .bind(::filterChars)          (2)
    .bind(::normalize)            (2)
    // Abridged for brevity
1 Create the class with the initial value set to the filename
2 Call the bind() function, which executes the function passed as a parameter. In turn, this function uses value as its parameter, and overwrites it with the result of its own execution.

Removing state

At this point, the program works and is the exact mirror of the Python sample. However, function composition comes from Functional Programming, and the later eschews mutable state. In the above snippet, the value property is constantly overwritten. Let’s remove it, and migrate to a completely immutable process.

The function that needs to be designed:

  1. starts from a value of a type
  2. executes a function with this value as a parameter
  3. and returns the result

This looks like:

fun <T, V> T.pipe(function: (T) -> V) = function(this)

The calling code can then be replaced with:

filename
    .pipe(::readFile)
    .pipe(::filterChars)
    .pipe(::normalize)

Icing on the cake, this version is fully type-safe. In the previous version, the returned value was the same instance of TheOne. Hence, the following compiled, but crashed at runtime:

TheOne(filename)
    .bind(::filterChars)      (1)
    .bind(::readFile)
1 filterChars expects a List<String> input, but value holds a simple String at this point

The current version will fail at compile-time:

filename
    .pipe(::filterChars)       (1)
    .pipe(::readFile)
1 filterChars still expects a List<String>, but the compiler is aware of the signature of pipe(): filename is a String, the T generic type is a String, hence the function passed to pipe() should accept a String parameter. Because this is not the case, the code won’t compile.

Conclusion

Modeling a function call as a mathematical function allows to chain them together - this is function composition. Some libraries are designed like that, such as Resilience4J, where every feature is a function: features can be composed together.

Unlike mathematics, function composition in software can be mutable or immutable. In all cases, function composition is definitely a good tool to have in a well-rounded developer’s toolbelt.

The complete source code for this post can be found on Github.
Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Composing Exercises in Programming Style
Share this