Streamline Your Data Processing with Kotlin’s Pipeline Pattern

Alex Manhooei
3 min readJan 1, 2023

The Pipeline pattern is a design pattern that allows you to execute a series of stages or steps in a sequential fashion. This pattern is commonly used in data processing pipelines, where each stage transforms the data in some way.

In Kotlin, you can implement the Pipeline pattern using a combination of higher-order functions and function composition. A higher-order function is a function that takes one or more functions as arguments, or returns a function as a result. Function composition is the process of combining two or more functions to create a new function.

Here is an example of a simple Pipeline pattern implemented in Kotlin:

fun pipeline(input: String, stages: List<(String) -> String>) = stages.fold(input) { acc, stage -> stage(acc) }

The pipeline function takes an input string and a list of stages, which are functions that take a string as input and return a string as output. The stages list is then processed using the fold function, which applies each stage to the input string in turn.

Here is an example of how you might use the pipeline function:

val stages = listOf(
{ s: String -> s.toLowerCase() },
{ s: String -> s.trim() },
{ s: String -> s.replace(" ", "-") }
)

val result = pipeline(" Hello World! ", stages)

In this example, the input string “ Hello World! “ is passed through three stages: lowercasing, trimming, and replacing spaces with hyphens. The final output of the pipeline is “hello-world”.

You can also use function composition to create more complex pipelines. For example, the following code defines a pipeline function that takes a list of stages and returns a function that applies the stages to its input:

fun pipeline(stages: List<(String) -> String>) = { input: String -> stages.fold(input) { acc, stage -> stage(acc) } }

This version of the pipeline function allows you to create a pipeline with a specific set of stages, and then apply it to different inputs as needed:

fun pipeline(input: String, stages: List<(String) -> String>) = stages.fold(input) { acc, stage -> stage(acc) }

fun main() {
val stages = listOf(
{ s: String -> s.toLowerCase() },
{ s: String -> s.trim() },
{ s: String -> s.replace(" ", "-") }
)

val result = pipeline(" Hello World! ", stages)
println(result)
}

The pipeline function is defined as before, and the main function creates a list of stages and applies them to the input string " Hello World! ". The output of the pipeline is then printed to the console.

You can also use function composition to create a pipeline and apply it to different inputs as needed:

fun pipeline(stages: List<(String) -> String>) = { input: String -> stages.fold(input) { acc, stage -> stage(acc) } }

fun main() {
val pipeline = pipeline(listOf(
{ s: String -> s.toLowerCase() },
{ s: String -> s.trim() },
{ s: String -> s.replace(" ", "-") }
))

val result1 = pipeline(" Hello World! ")
println(result1)

val result2 = pipeline(" Another String ")
println(result2)
}

In this example, the main function creates a pipeline with a specific set of stages, and then applies it to two different input strings. The outputs of the pipeline are then printed to the console.

Overall, the Pipeline pattern is a useful way to organize and execute a series of processing steps in Kotlin, and can help you write modular, reusable code that is easy to understand and maintain.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Alex Manhooei
Alex Manhooei

Written by Alex Manhooei

Staff Software Engineer @ Google. All of my blog posts are my personal opinions and not related to my work at google in any shape or form.

Responses (1)

Write a response

Is this pattern thought to be also used for steps which produce a different output?
Some kind of mapping pipeline. I was also wondering if this may be extended with Kotlin's Result class to have an error handling and leave the pipeline whenever needed.