Upcoming Change: Function Types Reform

Kotlin M12 will likely bring another change that is crucial for implementing a useful reflection library for Kotlin. In short, we are going to unify FunctionX and ExtensionFunctionX to be represented in the same way at runtime, but it will not affect our ability to create type-safe builders and other DSL-like constructs.

Why

Currently, there are 23*2 = 46 class files related to FunctionX in the kotlin package kotlin-runtime.jar and 46 more class files related to ExtensionFunctionX (X being a number between 0 and 22). This is a lot of class files already, but there are 46*3 = 138 more class files in the kotlin.reflect package (KFunctionX, KMemberFunctionX, KExtensionFunctionX), which is way over the top :)

So, for one thing, we really need to reduce the number of class files in kotlin-runtime.jar.

Then, at the moment ExtensionFunctionX types are not related to FunctionX types, and we can not say listOfStrings.map(String::length), because the argument has type String.() -> Int, but map() expects (String) -> Int, which is sad, annoying and inconvenient.

So, we want to make extension functions coercible to normal functions (with an extra parameter).

While we are at it, we would also like to allow functions with more than 22 parameters, theoretically any number of parameters (in practice 255 on JVM).

An important constraint here is that implementing Kotlin functions in Java must remain easy: Java 8 lambdas should work and in earlier versions of Java new Function2() { ... } with only invoke() method in the body should be enough.

How

All 230 function class files will be replaced by a single interface Function which will represent all functions at runtime (+ we will keep 23 interfaces kotlin.jvm.FunctionX to facilitate easy creation of Kotlin functions in Java). A total win of over 200 class files.

Now, function values (“(A) -> B“) and extension function values (“A.() -> B“) will be represented by the same type at runtime, which will allow using them almost interchangeably. The static type system will only distinguish arities (e.g. the compiler will create fictitious “classes” FunctionX for any X, which will exist at compile time only and will never be emitted as class files).

Since the compile-time types will not be exactly the same as runtime types, several tweaks in operators like is and as are required, but they will not affect the cases that are not statically known to involve function types.

The syntactic distinction between function- and extension function types will be preserved (through desugaring A.() -> B to an annotated function type): when a lambda is type-checked against an expected type which is an extension function, type inference adds a this-receiver to its signature, and for a normal function it adds a parameter instead:

Consequences

These changes will make reflection on functions possible (i.e. KClass will finally have getFunctions()), and using function objects will be more intuitive.

You won’t need to change anything in your code unless you referenced ExtensionFunctionX types directly (by saying, e.g. ExtensionFucntion0<Foo, Unit>).

For more details, see this spec document (you can comment on the source, or press “View” for rendered markdown).

About Andrey Breslav

Andrey is the lead language designer of Kotlin at JetBrains.
This entry was posted in Language design. Bookmark the permalink.

11 Responses to Upcoming Change: Function Types Reform

  1. Great stuff, thanks for the regular updates.

    Will this change make it easier to address ? I can live without it, but it would be a nice addition to the language.

  2. Salomon BRYS says:

    This is great news !
    IMHO, being able to write .reduce(String::size) instead of .reduce { it.size() } is a big win for readability :)

  3. Daniel Rothmaler says:

    Looks very good… :-)

    I know it’s not the same thing, but if you rework the handling of functions/extension functions anyway, maybe you could also consider KT-5261 (make it possible to use static Java functions, as extension functions [on it’s first parameter])…

  4. Даниил Водопьян says:

    Am I correct that the folowing code will work?

    Here I used the truncate function in a place where a lambda with many parameters were expected.

  5. Rostislav says:

    What about Java interoperability? What if some Kotlin api exports some new function type? And on Java side we would like to check for instance of Function2.
    Reflection in Java would be a bit strange, do I understand that correctly?

    • Yes, there will be some caveats in Java interop, although not that bad:

      • if a Kotlin function returns kotlin.Function2, a Java method will return kotlin.jvm.Function2, not kotlin.Function, so Java interop will be smooth enough in this case
      • since FunctionImpl implements all 23 function interfaces, Java’s instanceof won’t work correctly (will return true for any function), and you’d need to use an API function provided by Kotlin to check for function arity
  6. jan kotek says:

    Some classes are generated at runtime, does that work in restricted envirnments? JME, android, applets, j2ee containers…

    • Jayson says:

      Applets? jajajajjajajajajja…. sorry, just imagining kotlin versions of tumbling duke applets. Probably ok to say “Don’t worry about Applet support” for now. Java is good at those. J2ME seems as irrelevant these days. J2EE containers support most things you want within your class loaders. That leaves “Does classes generated a runtime work on Android?” as the question. And since Kotlin teams seems to care a lot about Android (compiler plugin for android support, Anko framework, etc.) I think they probably have this covered.

    • No, classes are not generated at runtime. Unless your platform supports Java 8, this will remain so (Java 8 uses bytecode spinning for lambdas, and it works on any platform that supports Java 8).

Comments are closed.