Skip to main content

Kotlin Function Composition

Introduction

Function composition is a fundamental concept in functional programming that allows you to combine multiple functions to create a new function. Instead of calling functions sequentially, composition creates a new function that represents the combined operation. This technique helps write cleaner, more modular code by building complex behavior from simpler, reusable functions.

In this tutorial, we'll explore function composition in Kotlin, starting with the basic concepts and gradually moving to more practical examples.

Understanding Function Composition

At its core, function composition is about taking the output of one function and passing it as input to another function. If we have two functions f(x) and g(x), their composition is typically written as (f ∘ g)(x) in mathematics, which means f(g(x)).

In Kotlin, we can achieve this using higher-order functions.

Basic Function Composition in Kotlin

Let's start with a simple example to understand function composition:

kotlin
// Define two simple functions
val square = { x: Int -> x * x }
val addOne = { x: Int -> x + 1 }

// Manual composition (without a compose function)
val squareThenAddOne = { x: Int -> addOne(square(x)) }
val addOneThenSquare = { x: Int -> square(addOne(x)) }

fun main() {
val number = 5

println("Original number: $number")
println("Square then add one: ${squareThenAddOne(number)}") // (5² + 1) = 26
println("Add one then square: ${addOneThenSquare(number)}") // (5 + 1)² = 36
}

Output:

Original number: 5
Square then add one: 26
Add one then square: 36

Notice how the order of composition matters! Applying functions in a different order can lead to different results.

Creating a Compose Function

While we can manually compose functions as shown above, it's more elegant to create a dedicated compose function:

kotlin
// Define a compose function for two functions
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
return { x -> f(g(x)) }
}

fun main() {
val square = { x: Int -> x * x }
val addOne = { x: Int -> x + 1 }

// Using our compose function
val squareThenAddOne = compose(addOne, square)
val addOneThenSquare = compose(square, addOne)

val number = 5

println("Square then add one: ${squareThenAddOne(number)}") // 26
println("Add one then square: ${addOneThenSquare(number)}") // 36
}

Extension Function for Composition

We can make function composition even more elegant using Kotlin extension functions:

kotlin
// Extension function for function composition
infix fun <A, B, C> ((B) -> C).compose(g: (A) -> B): (A) -> C {
return { x -> this(g(x)) }
}

fun main() {
val square = { x: Int -> x * x }
val addOne = { x: Int -> x + 1 }
val multiplyByTwo = { x: Int -> x * 2 }

// Using infix notation for composition
val composed = addOne compose square compose multiplyByTwo

val number = 3
// This executes: addOne(square(multiplyByTwo(3)))
// = addOne(square(6))
// = addOne(36)
// = 37
println("Result: ${composed(number)}") // 37
}

Note that with the infix notation, functions are composed from right to left. The function on the right is applied first, then its result is passed to the function on the left.

Practical Example: Text Processing Pipeline

Let's create a more practical example - a text processing pipeline that:

  1. Removes punctuation
  2. Converts to lowercase
  3. Splits into words
  4. Filters out short words
kotlin
fun main() {
val removePunctuation = { s: String -> s.replace(Regex("[^\\w\\s]"), "") }
val toLowerCase = { s: String -> s.lowercase() }
val splitIntoWords = { s: String -> s.split(Regex("\\s+")) }
val filterShortWords = { words: List<String> -> words.filter { it.length > 3 } }

// Create a processing pipeline using composition
val processText = filterShortWords compose splitIntoWords compose toLowerCase compose removePunctuation

val text = "Hello, World! Functional programming in Kotlin is really fun."
val result = processText(text)

println("Original text: $text")
println("Processed result: $result")
}

Output:

Original text: Hello, World! Functional programming in Kotlin is really fun.
Processed result: [hello, world, functional, programming, kotlin, really]

Advanced Composition: Multiple Inputs and Outputs

So far, we've looked at composing functions with a single input and output. But what about functions with multiple parameters?

Here's how we can handle that:

kotlin
// A function that takes two parameters
fun add(a: Int, b: Int): Int = a + b

// A function that processes the result
fun square(x: Int): Int = x * x

// Composing them
fun addThenSquare(a: Int, b: Int): Int = square(add(a, b))

fun main() {
val result = addThenSquare(3, 4)
println("Add then square: $result") // (3 + 4)² = 49
}

For more complex scenarios, we can use partial application or currying:

kotlin
// Convert a two-parameter function to a curried form
fun <A, B, C> curry(f: (A, B) -> C): (A) -> (B) -> C {
return { a -> { b -> f(a, b) } }
}

fun main() {
val add = { a: Int, b: Int -> a + b }
val square = { x: Int -> x * x }

val curriedAdd = curry(add)
val add5 = curriedAdd(5) // Creates a function that adds 5 to its input

// Now we can compose these functions
val add5ThenSquare = square compose add5

println("Add 5 then square: ${add5ThenSquare(3)}") // (3 + 5)² = 64
}

Real-World Application: Data Transformation Chain

Let's look at a more real-world example where function composition shines - processing a list of user data:

kotlin
data class User(val id: Int, val name: String, val email: String, val isActive: Boolean)

fun main() {
// Sample user data
val users = listOf(
User(1, "Alice", "[email protected]", true),
User(2, "Bob", "[email protected]", false),
User(3, "Charlie", "[email protected]", true),
User(4, "Diana", "[email protected]", false)
)

// Define individual transformation functions
val getActiveUsers = { userList: List<User> -> userList.filter { it.isActive } }
val extractEmails = { userList: List<User> -> userList.map { it.email } }
val formatForDisplay = { emails: List<String> -> emails.joinToString(", ") }

// Compose the functions into a processing pipeline
val getActiveUserEmails = formatForDisplay compose extractEmails compose getActiveUsers

// Execute the pipeline
val result = getActiveUserEmails(users)

println("Active user emails: $result")
}

Output:

Benefits of Function Composition

  1. Reusability: Small, focused functions can be reused in different compositions
  2. Readability: Once you understand the pattern, composed functions can be easier to understand
  3. Maintainability: You can modify one function without affecting others
  4. Testability: Smaller functions are easier to test individually

Common Pitfalls

  1. Type Compatibility: Ensure that the output type of one function matches the input type of the next
  2. Function Order: Remember that composition order matters
  3. Debugging Complexity: It might be harder to debug composed functions

Summary

Function composition is a powerful technique in functional programming that allows you to build complex behavior by combining simpler functions. In Kotlin, we can implement composition using higher-order functions, extension functions, and infix notation to create elegant, readable, and maintainable code.

Key takeaways:

  • Function composition combines functions where the output of one becomes the input of another
  • The order of composition matters
  • Using infix notation provides a readable syntax for composition
  • Composition helps build reusable processing pipelines

Exercises

  1. Create a composition function that works with three functions instead of two
  2. Implement a function pipeline that processes a list of strings by:
    • Converting each to uppercase
    • Filtering out strings shorter than 4 characters
    • Sorting them alphabetically
  3. Create a more complex data processing pipeline for a list of products with prices, applying discounts and taxes
  4. Explore how to handle error cases in function composition (hint: look into Either or Result types)

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)