Can I avoid boxing when decorating lambda?

Hey :wave:

I want to decorate a generic lambda, and I discovered that the compiler assumes boxed types. Let me show that on an example:

inline fun <reified T1, reified T2, reified R> decorate(noinline l: (T1, T2) -> R): (T1, T2) -> R {
  return { a: T1, b: T2 -> l(a, b) }
}

When invoking decorate like this:

decorate { a: Double, b: Double ->
  a + b
}

the returned object will have an invoke function with the following signature:

invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object

That is probably dictated by how Java handles generics. In a broad context, using primitives and generics won’t work; however, here, I don’t see a clear reason why the compiler couldn’t use primitives to generate the invoke function. Is there any way of forcing the compiler to use primitive types?

Heya!

How about crossinline instead of noinline, so that the block is inlined (so boxing isn’t needed), but just acknowledging some restrictions on l because it’s called through a non-inline lambda

You might still end up with boxing though because that might be unavoidable with the stdlib lambda types with their generics

1 Like

Yeah this is a Java thing, not a Kotlin thing. It’s because your return value is { a: T1, b: T2 -> l(a, b) }. That creates a lambda, and as far as I understand it in Java, a lambda is an interface with an anonymous class implementing the interface. The interface uses generics to indicate the signature, so you have to use boxed types. If we were to write out this actual lambda in Java, it’d look like this:

BiFunction<Double, Double, Double> returned = (a, b) -> a + b;

You can’t do BiFunction<double, double, double> because generics have to be boxed types.

I’ve tried both versions using ‘inlined’ and ‘crossinline’. In my case, the first one produces better results. At least R8 could eliminate some boxing.

I know that generics must be boxed, and I can’t create a BiFunction with double’s. However, Kotlin is smart enough to use primitives in some situations. For instance:

inline fun <reified T1, reified T2, reified R> create(noinline l: (T1, T2) -> R): Any {
  return l
}

will create a bytecode that uses primitives when invoked like this: create { a: Double, b: Double -> a + b }. I don’t know why the same optimization isn’t possible when I want to decorate the lambda parameters.

1 Like

Maybe it’s possible and for some reason it hasn’t been done yet. Reporting might be a good idea.

In the second case (the one with create), can you indicate how you figure out that the bytecode uses primitives ? I have been unable to reproduce this behavior. I can see an object with two invoke functions, one taking primitives and the other taking boxed types ; I can get the code with decorate behave the same way by massaging it to get it closer to the code using create. But as I understand it, what will matter to you is whether the Doubles are boxed when the function is called, and since the object has both the boxed and unboxed version this falls short of exhibiting the behavior you want ? Importantly, note that because create is declared as returning Any, you cannot call the method on this object without downcasting, and I do not think you’ll be able to downcast it to any object that will use the primitive types (short of declaring the specialized type and using it everywhere – which would void the point of using generics).

So if I understand correctly, in Kotlin (A, B) -> C is purely syntactic sugar for Function2<A, B, C>. That means that as long as you write this return type explicitly, or taking this argument type explicitly, you are explicitly requesting the boxing behavior.

I do not know of any way to ask Kotlin to compile this code without defining a type somewhere, and at that point, either you define the specialized type (then you have to do it for each different primitive you want) or you define a type with generics (then you have boxing). Structural typing would probably be the modern way to write this with no explicit declarations, but Kotlin doesn’t have structural typing.

So all in all I don’t think this is possible today.

Note that for most applications, boxing is no concrete problem at all. I observe that most cases where programmers want to get rid of boxing is more out of some feeling of wastefulness at boxing rather than an actual performance need. Maybe in your case you actually need the additional performance, but I’d encourage you to consider whether this is not premature optimization ; consider especially how this looks relatively easy to fix later (when you measure that boxing is really an issue) at the cost of additional complexity by defining ad-hoc types with the methods taking the primitives for each combination you need :

interface Function2Double {
  fun invoke(a : Double, b : Double) : Double
}

inline fun decorate_double(crossinline l : (a : Double, b : Double) -> Double) : Function2Double {
  return object : Function2Double { override fun invoke(a : Double, b : Double) = l.invoke(a, b) }
}

decorate_double { a : Double, b : Double -> a + b } // doesn't box