How to chain lambdas to a resulting lambda in Kotlin?

Viewed 312

... or the equivalent of java.util.Function.andThen()

In Java

Function<String, String> add1 = string -> string + "1";
Function<String, String> add2 = string -> string + "2";
Function<String, Strint> add12 = add1.andThen(add2);

add12.apply("") returns "12"

How would I write it in Kotlin?

val add1 = { string:String -> string + "1" }
val add2 = { string:String -> string + "2" }
val add12 = ?
3 Answers

The feature you're looking for is called function composition. As far as I can tell, it doesn't come built-in to Kotlin (would love to be corrected on this). But it's very easy to write as an extension function.

infix fun<A, B, C> ((B) -> C).compose(that: (A) -> B): (A) -> C =
  { this(that(it)) }

Now we can write

val add1 = { string:String -> string + "1" }
val add2 = { string:String -> string + "2" }

println((add2 compose add1)("3")) // Prints "312"

I write compose to use right-to-left composition, more in line with the way mathematical functions work.

Granted, this is not exactly what you're looking for, because you can't store a composed function this way in a variable, but you can chain the results of functions using run if the functions themselves don't have the parameter as a receiver:

fun print(string: String) {
    println(add1(string).run(add2))
}

// or

fun print(string: String) {
    println(string.run(add1).run(add2))
}

Since run is an inline function, it doesn't add a wrapper object around each function.

The let function will have the exact same effect. This is because when you pass something other than a lambda to a higher order function, it doesn't matter if the first parameter is a receiver or not. They are treated as the same signature.

If you are that familiar with Java functions and/or want to use them, you are still open to do that (using java.util.function.Function):

val add1 : Function<String, String> = Function { "${it}1"}
val add2 : Function<String, String> = Function { "${it}2"}
val add12: Function<String, String> = add1.andThen(add2)

If I wanted to have something similar in Kotlin, I would probably just go for what also Tenfour04 showed, i.e. use either let or run:

val add1 : (String) -> String = { "${it}1"}
val add2 : (String) -> String = { "${it}2"}
val add12 : (String) -> String = { it.let(add1).let(add2) } // or: { add1(it).let(add2) }

If you compare the two you only spare something, if you omit the type, but it's still clear enough what gets composed.

Of course you can implement your own compose or andThen-functions. However, if you don't mind using an additional library, you may rather be interested in Arrow where lots of functional use-cases are already supported.

Related