Published on

How to Pass Method References Around in Kotlin and Build a Simple Pipeline of Functions

Authors

I recently read a very interesting article about how an experienced object orientated developer worked an a project where the team on the project decided to adopt a functional approach to developing their code. I also watched this video by Rich Hickey (the creator of Clojure) where he speaks about the importance of simplicity and striving for it in software development. One of the areas where the article and video overlap is how using a functional approach to development allows you to break your code up into smaller pieces that are easier to code, read and test. Using a functional approach is also awesome as you can very easily compose these little functions together to do new things.

This got me to wondering, how easily can I pass methods around in Kotlin? By this I mean if I have a method I want to pass around how do I return the method reference? So for example I have the following method:

fun myAwesomeMethod(): String {
   //do cool stuff
   //...
   return coolStuff
}

To refer to this method in another one I would write it like this:

fun awesomeBringer(someAwesomeMethod: () -> String) {
   //...
   val coolStuff = someAwesomeMethod()
   //...
}

Now if I instead want to pass this around I would do this:

fun awesomeUser(): () -> String {
   //guarantee some other stuff happens here then return a method reference
   //...
   return ::myAwesomeMethod
}

Using this approach I can for example easily implement a pipeline of functions. In this case I am building up a pipeline to process Strings in a certain way:

fun stringPipelineComposer(vararg processor: (String) -> String): Set<(String) -> String> {
   return processor.toSet()
}

fun processString(valueToProcess: String, pipeLine: Set<(String) -> String>): String {
   return pipeLine.fold(valueToProcess) { transformedValue, processor ->
            processor(transformedValue)
   }
}

I then build up and use the pipeline:

...
val stringProcessorPipeline = stringPipelineComposer(::trimAllLeadingSpaces, ::trimAllTrailingSpaces, ::capitilize, ::removeDuplicateWords)
val processedString = processString(someInputThatNeedsToBeProcessed, stringProcessorPipeline)

As you can see from the above each function can easily be tested in isolation. We then compose them all together and process a string where we can test the entire composition easily. I intentionally used a Set to indicate that the ordering of the functions is irrelevant and that a function cannot be used more than once.