The Programmers Guide To Kotlin - Inline Functions
Written by Mike James   
Tuesday, 25 June 2019
Article Index
The Programmers Guide To Kotlin - Inline Functions
Non-local returns
Reified Type

Non-Local Returns

The biggest impact that inlining has is that any return statements that you include in a lambda will appear in the inline function as if they were returns from it. This sounds complicated, but it is trivial if you keep in mind the re-writing process. When the compiler writes the code of an inline function at the call site it removes any returns - there is nothing to return from. However, when it inlines a lambda parameter it doesn't remove any returns and these are simply placed in the code.

This sometimes produces results that you want and it sometimes doesn't.

It is difficult to find a simple example that is realistic, but consider the following set of functions.

First an inline function that accepts a lambda of type arith, evaluates it and prints the result:

inline fun myEvaluate(f:arith){
    var result=f(1,2)
    println(result)    
}

Next a noinline function which uses myEvaluate:

fun myNonInline(){
    println("before")
    myEvaluate({a, b -> a+b})
    println("after")  
}

If we call myNoninline in main:

fun main(args: Array<String>) {
    myNonInline()
}

you see exactly what you would expect:

before
3
after

Now try changing the lambda to:

fun myNonInline(){
    println("before")
    myEvaluate({a, b -> return a+b})
    println("after")
}

i.e. add return in front of the returned value. What you get is an error:

kotlin1

If you are not aware that myEvaluate is an inline function then this is mystifying as myEvaluate is declared as returning an Int - where does the Unit come from?

The answer is that myEvaluate is inlined into the body of myNonInline as is the lambada – including the return. The inlined code is equivalent to:

fun myNonInline(){
    println("before")
    var result=return 1+2
    println(result)   
    println("after")
}

Now you can see where the Unit comes from. The return is from myNonInline function and it has a Unit return type.

To make this work you have to change myNonInline to return an Int:

fun myNonInline():Int{
    println("before")
    myEvaluate({a, b -> return a+b})
    println("after")
   return 1
}

Now the error message vanishes and you can run the code but what you see might still be surprising. All you see printed is "before". This is perfectly obvious when you know that the code is equivalent to:

fun myNonInline():Int{
    println("before")
    var result=return 1+2
    println(result)   
    println("after")
    return 1
}

Clearly the return 1+2 ends the myNonInline function which you can demonstrate using:

println(myNonInline())

which prints 3.

A return in an inlined lambda passed to an inlined function is a return from the outer function with uses the inlined function.

As already mentioned sometimes this non-local return behavior is an advantage, but most of the time it isn't.

The example given in the documentation explains that non-local returns are desired behavior in functional loops.

For example:

fun hasZeros(ints:List<Int>):Boolean{
 ints.forEach{
   if(it==0) return true
 }
 return false
}

In this case forEach is assumed to be an inline function and hence its lambda parameter performs a non-local return which ends the outer noninline function.

Reified Type

 The final consequence of inlining a function is the reification on type parameters. Reified means to make real and in this case what appears to be an advanced concept is just another simple consequence of the re-writing involved in an inline function. When you create a standard generic the type that is assigned is lost or erased at run time because all generic parameters are implemented as Any.

What this means is that you cannot write the following simple function:

fun <T> IS(obj:Any):Boolean{
   return obj is T
}

It should return true or false depending on what type obj and T are. Unfortunately this doesn't work.

This isn't possible at run time because the type of the object is always Any.

We can make it work by reifying the type parameter as part of the inline rewriting process. If you just make the function inline then the generic type will not be reified and you will still be working with an object of type Any. You also have to add reified to the type parameter:

inline fun <reified T> IS(obj:Any):Boolean{
   return obj is T
}

with these small changes the function works and:

IS<Int>("23")

returns false but:

IS<int>(23)

returns true.

<ASIN:1871962536>

<ASIN:1871962544>



Last Updated ( Tuesday, 25 June 2019 )