Upcoming Change: Function Types Reform

Andrey Breslav

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:

// Normal function:
fun call(callback: (Foo) -> Unit) { // callback has type `Function1<Foo, Unit>
    ...
}

// argument is a lambda with a normal parameter
call { foo -> println(foo) }

// Extension function
fun builder(body: Foo.() -> Unit) { // body has type `@extension Function1<Foo, Unit>`
  ... 
}

// argument is an extension lambda: no parameters, but `this` is available
builder {
    this.bar() // `this` has type Foo
}

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).

Comments below can no longer be edited.

11 Responses to Upcoming Change: Function Types Reform

  1. Ioannis Tsakpinis says:

    April 9, 2015

    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.

    • Andrey Breslav says:

      April 9, 2015

      It will not affect this request in any way

  2. Salomon BRYS says:

    April 9, 2015

    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:

    April 9, 2015

    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:

    April 9, 2015

    Am I correct that the folowing code will work?

      fun abcTest(f: (String, Int, Int)-&gt; Unit) = f("abc", 1, 2)
    
      fun String.truncate(begin: Int, end: Int) = (this as CharSequence).slice(begin..end)
    
      fun main(args : Array) {  
        abcTest(String::truncate)
      }
    

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

    • Andrey Breslav says:

      April 9, 2015

      Apart from the fact that the expected return type of the function is Unit, but your function returns something else, it will work.

  5. Rostislav says:

    April 10, 2015

    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?

    • Andrey Breslav says:

      April 10, 2015

      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:

    April 13, 2015

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

    • Jayson says:

      April 13, 2015

      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.

    • Andrey Breslav says:

      April 13, 2015

      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).