I decided to try something fun to see how it would work in Kotlin vs. how it works in Java8. First, here’s a simple Java8 class that uses function composition (with apologies in advance to Bruno Mars and Mark Ronson):
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
public class JavaFunc {
@NotNull
public static <T> Function<T,T> uptown(@NotNull Function<T,T> funkYouUp) {
return funkYouUp.andThen(funkYouUp).andThen(funkYouUp);
}
}
And here’s a Kotlin class that uses it, as well as tries to do the same thing itself without calling out to Java:
fun getFuncy(s: String): String = "%s %s".format(s,s)
// this, as well as something akin to Java's Function.compose, should probably be built into Standard.kt
infix fun <F: (T1)->T2,T1,T2,T3> F.andThen(g: (T2)->T3): (T1)->T3 = { g(this(it)) }
// the Java code is doing the equivalent of this line here
fun <T> uptown(funkYouUp: (T)->T) = funkYouUp andThen funkYouUp andThen funkYouUp
fun main(args: Array<String>) {
val refrain = "Uptown func you up!"
val func = ::getFuncy
// val funcYouUp: (String) -> String = JavaFunc.uptown(func) // fails because Kotlin can't translate from java.util.Function
val funcYouUp = JavaFunc.uptown(func)
System.out.println(funcYouUp.apply(refrain)) // ugly, but works
val funcYouUp2 = uptown(func)
System.out.println(funcYouUp2(refrain)); // this is what I want the Java8 version, above, to look like
}
A couple things are worth noting about this exercise:
-
Standard.kt
should probably have function composition operators, likeandThen
above, as a standard built-in feature for everybody. They’re really useful. - Kotlin’s interop with Java8 lambdas isn’t as good as it could be. Notably, the inferred type of
funcYouUp
isFunction<String!,String!>!
with no easy way to annotate the Java, beyond what I’ve already done with@NotNull
to nuke all those!
things, much less get the inference all the way to(String)->String
, which is what I really want. - If I try deleting the return type of
andThen
(i.e., remove the: (T1)->T3
part), then Kotlin cannot infer it, giving the unhelpful error “unresolved reference: it”. If I change the lambda to read{ x -> g(this(x)) }
, it then highlights thex
and says “cannot infer a type for this parameter”. The error message for the “it” lambda form should be the same as for the declared parameter-name form, and really the compiler should be able to figure out either one without needing the return-type declaration. - Nonetheless, it’s exceptionally cool to be able to declare an extension function on lambdas, and have it just magically work.