Wednesday, February 10, 2021

Reified, crossinline, noinline and inline everything?

https://www.mememaker.net/static/images/memes/4773372.jpg

 

This is a continuation of the previous blog post.

As promised this is the blog post where we'll talk more about the mysterious inline, crossinline and not so confusing noinline and my most favorite, reified.

What is inlining?

Let's say you have a
open class Dog {
fun bark() {
// call printBarkMessage
printBarkMessage()
}

fun printBarkMessage() {
println("meow, ughhm woof?")
}
}
Inlining is a way to optimize compiled source code at runtime with the help of the JIT (just in time compiler) by replacing the invocations of the most often executed methods with their bodies.

When inline happens we have the following
open class Dog {
fun bark() {
// call printBarkMessage
println("meow, ughhm woof?") <---- inlining hpapens
}

fun printBarkMessage() {
println("meow, ughhm woof?")
}
}

If another class was to extend Dog then we have to make our printBarkMessage() open and override it and we have
class Husky : Dog() {
override fun printBarkMessage(){
println("woofz and more woofz")
}
}
then essentially when inlining happens, the compiler will be stuck with the
fun printBarkMessage() {
println("meow, ughhm woof?")
}
which would be wrong in this case, since the object was actually an instance of Husky.

Well this is Java's world, which applies to Kotlin but with some smart decisions done by the Kotlin team.

If we wre to add inline modifier to printBarkMessage and override the printBarkMessage in our Husky class we'd get

it suggests you to make it open, but even if you do

you can't inline it, Kotlin has solved the problem for us but does it end here?

Not quite.

In case you want only some of the lambdas passed to an inline function to be inlined, you can mark some of your function parameters with the noinline modifier:
inline fun bragAboutHowIAmUsingLinux(arch: () -> Unit, noinline ubuntu: () -> Unit) { ... }

If we were to write this function
inline fun stopUsingJava(body: () -> Unit) {
val migrateToKotlinRunnable = Runnable { body() } <--- won't compile
migrateToKotlinRunnable.run()
}
but why it doesn't compile?

First, an inline function that call the lambdas passed to it as parameters not directly from the function body, can't return out of a nested lambda, first let's take a look of a case where you can return

fun hasBitcoin(listOfNames: List<String>): Boolean {
listOfNames.forEach {
if (it == "Elon Musk") return true
}
return false
}
this isn't an inlined function and it's body is not copy pasted around as an inline would and the compiler knows about its return and it's not nested inside another lambda (forEach is an only one) but forEach is a lambda, now how do we fix stopUsingJava?

inline fun stopUsingJava(crossinline body: () -> Unit) {
val migrateToKotlinRunnable = Runnable { body() }
migrateToKotlinRunnable.run()
}
Since body can't do non local returns and the function is an inlined one, body has to be marked with crossinline, you requested to copy-paste the code of the block into a place that expects a value by doing so it can't contain non-local control flow, what does this mean? 
You can't use return.

Inlining does some pretty good optimizations when all functional type parameters are called directly or passed to other inline function/s and if at least one of your functional type parameters can be inlined, use noinline for the others.
 
Another use case is reified type parameters, which require you to use inline (you're forced to inline them), but why?

For example
fun <T> String.toKotlinObject(): T {
val mapper = myCustomObjectMapper() //does not compile!
return mapper.mapPojoValue(this, T::class.java)
}
inline fun <reified T: Any> String.toKotlinObject(): T {
val mapper = myCustomObjectMapper()
return mapper.mapPojoValue(this, T::class.java)
}
Alright, what's happening here?
 
You tell the compiler to copy the function's body (bytecode) to every line the function is invoked from (also known as inlining) from there on
when you combine an inline function with reified type, the compiler knows the actual type passed as a type argument so that it can modify the generated bytecode to use the corresponding class directly.
 
If you have a call like myVariableFromServer is T becomes myVariableFromServer is Husky in the bytecode (if the type argument is Husky).

But how does this happen actually?

You probably heard of the interface Type, which is the superinterface of all types in the JVM.



As you can see Class is implementing this interface

If you access an Any::class.java.genericSuperClass it returns a Type
but Type won't do us any good since it only hold a name describing itself but we can cast the Type to a ParameterizedType


and now we have an array of actual type arguments that hold the object representing the actualy type argument, we need the first object only.

val type: Type?
get() = (this::class.java.genericSuperclass as ParameterizedType).actualTypeArguments.firstOrNull()

and now if we were to have the class information for a String it will print out
class java.lang.String
and that's how reified was born.

We have learned about inlining and reified and now every time you think of passing a lambda to your function (apart from being forced to use inline when you need the type parameter) you think of inlining it huh?
I don't blame you, your brain tricks you into inlining it just as it did tricked me.

Why won't you need to do that?
As explained in the previous blog post

Additionaly JVM has limits of up to 64K bytecode instructions for a single method, careful how you tread with inlining, it can be something you're proud of, with great abstraction comes great overhead, either for you as a programmer or for the compiler.

Hope you learned something new today.
Back to writing Kotlin.

https://danlowe.me/img/20180316/theresmore.jpeg


You can inline properties you know?

val foo: Fighters
inline get() = Fighters()

var system: OfADown
get() = ...
inline set(value) { ... }
or the whole property which marks both the getter and the setter as inlined

inline var tool: Tool
get() = ...
set(value) { ... }

also when you have an internal property value annotate it with
@PublishedApi
so that you can use it inside an inlined function.

And also a function, constructor and a class can be marked with @PublishedApi, then instead of internal they're public for the inlined functions.
 
From today's knowledge we can write a simple exercise for an infamous event provider.
object RxBus {

@PublishedApi
internal val publisher = PublishSubject<Any>()

fun publish(event: Any) {
publisher.onNext(event)
}

inline fun <reified T> listen(): Observable<T> = publisher.ofType(T::class.java)

}
 
 


P.S: sometimes I write good code