Proposal: Add opportunity to pass primitive type arguments in Inlined lambda "by reference"

The passed argument can be a "reference " to primitive type value in outer scope, where lambda will inlined.
so change this argument in lambda actually change the value in outer scope.

att: @abreslav

1 Like

This feature is potentially very dangerous, since it would allow accidental change of the outer scope values due to name clash and a lot of abuse. Do you have any actual use-cases for that?

3 Likes

Name shadowing should not be a problem. It is easy enough to recognize this and have the compiler handle it appropriately. Aliasing can be a problem (two parameters actually having the same input variable). More important though is that doing this will make that inline functions have different semantics than non-inline functions. Of course this is already true with non-local returns, but the complexity in that is mainly in the inline function itself.

If we are talking about parameters only, not capturing the whole context, then it could be done, still, there are a lot of things that could go wrong. For such dramatic changes, there should be some critical use-case, that could not be solved without it.

1 Like

All this ā€œinline stuffā€ in Kotlin mostly related to the performance.

Inline class let you avoid Boxing/Wrapper classes that you have use in Java.
In Kotlin/Java functions cannot return multiply values.
If you need, you can to pack values in some container and then return container instead.

Passing to inline lambda arguments ā€œby referenceā€ let us escape from creating containers

inline fun foo(inlined: (var d0: Int, var d1: Int, var d2: Int, var d3: Int, var d4: Int) -> Unit) {
    var outer_d0 = 0
    var outer_d1 = 0
    var outer_d2 = 0
    var outer_d3 = 0
    var outer_d4 = 0

    inlined(outer_d0, outer_d1, outer_d2, outer_d3, outer_d4)

    //outer_d0 == 0
    //outer_d1 == 1
    //outer_d2 == 2
    //outer_d3 == 3
    //outer_d4 == 4
}

fun main() {
   foo() { d0, d1, d2, d3, d4 ->
        d0 = 0
        d1 = 1
        d2 = 2
        d3 = 3
        d4 = 4
    }
}

here as example,inlined lambda, var parameters are passed ā€œby referenceā€

@cheblin can you perform a JMH test to prove the performance improvements using an hand made implementation?

Your proposal makes the code harder to read.

2 Likes

Sometimes it is necessary for business logic to create a wrapper around some type. However, it introduces runtime overhead due to additional heap allocations. Moreover, if the wrapped type is primitive, the performance hit is terrible, because primitive types are usually heavily optimized by the runtime, while their wrappers donā€™t get any special treatment.
To solve such issues, Kotlin introduces a special kind of class called an inline class

open a new topic to discuss that Kotlin team is going to wrong directionā€¦

makes the code harder to read.

easy reading code is always good, but itā€™s written not for good looking onlyā€¦
Hint: Just ignore features that hard for you, but let others make there own choice

@cheblin we have already discussed inline classes in the issue 104, but your suggestion is a special use case, so you cannot simply copy-paste that motivation.
Again, please consider to measure supposed enhancements.

1 Like

special case? I do not think so.
My proposal is just small, useful extension of inlining featureā€¦

There is no problem with proposal for new features, but this one will need to be specified better. Providing measurements that prove that improvements provided by the feature are substantial enough to warrant it, as @fvasco suggests is a good start.

I would also suggest you propose another syntax, because now syntax is being reused for completely different semantics. Given the following line of code from your example:

I would conclude that:

  • This is an error, because you cannot assign to parameters in Kotlin.
  • This code does nothing useful, because it only assigns to local variables.

If the syntax is different, then the code becomes more readable. Here is an example (I have not thought this through, so it likely cannot be implemented like this):

inline fun foo(inlined: (ref Int, ref Int, ref Int, ref Int, ref Int) -> Unit) {
    var outer_d0 = 0
    var outer_d1 = 0
    var outer_d2 = 0
    var outer_d3 = 0
    var outer_d4 = 0

    inlined(outer_d0, outer_d1, outer_d2, outer_d3, outer_d4)

    //outer_d0 == 0
    //outer_d1 == 1
    //outer_d2 == 2
    //outer_d3 == 3
    //outer_d4 == 4
}

fun main() {
   foo() { ref d0, ref d1, ref d2, ref d3, ref d4 ->
        d0 = 0
        d1 = 1
        d2 = 2
        d3 = 3
        d4 = 4
    }
}

I suggest you propose another syntax

I did.

But my main point mostly about the featureā€¦implementation syntax may vary, up to Kotlin team.

Providing measurements that prove

let see

    // outer variables
    var outer_d0 = 0
    var outer_d1 = 0
    var outer_d2 = 0
    var outer_d3 = 0
    var outer_d4 = 0

    //code expansion with proposed feature 

    outer_d0 = 0
    outer_d1 = 1
    outer_d2 = 2
    outer_d3 = 3
    outer_d4 = 4

    // Current available code expansion with return array

    var d0 = 0
    var d1 = 1
    var d2 = 2
    var d3 = 3
    var d4 = 4

    val ret = intArrayOf(4)
    ret[0] = d0
    ret[1] = d1
    ret[2] = d2
    ret[3] = d3
    ret[4] = d4

    outer_d0 = ret[0]
    outer_d1 = ret[1]
    outer_d2 = ret[2]
    outer_d3 = ret[3]
    outer_d4 = ret[4]

still need some measure?

You did not change they syntax of the lambda. I think that part is the likely source of confusion. And in my opinion reusing var for a second concept is a bad idea.

What you show is not a measurement. It is an educated guess based on the amount of allocated objects you see in the code.

2 Likes

No, you changed the syntax of the function parameter type. You did not change the syntax of the lambda. I will repeat the lambda again below:

foo() { d0, d1, d2, d3, d4 ->
    d0 = 0
    d1 = 1
    d2 = 2
    d3 = 3
    d4 = 4
}

How can I know that d0 to d4 are references instead of regular parameters at this call site? Note: This is what I meant about confusion.

There is a huge difference between changing a var x: Int and an x: ref Int. Using var for both depending on the context, makes Kotlin less consistent.

Your code expansion is fine. But your assumption that the creation of an additional array will have a significant impact on performance is not a measurement.

I also think that you can measure the difference in a JMH test, but I doubt you will be able to show a significant effect in real-world scenarios. But these are educated guesses by me.

1 Like

If you look at any variable or passed argument, you never guess it type or variability, if there declaration is out of the screen.

It was a time then good practice was append variable type name to the variable nameā€¦ bool_VarSomthing

Nowadays IDE provide this information for you when you coding.

significant impact on performance

I am writing binary protocol parser code generator, by specification, on different languages C,C++, Rust,C#, Scala and now Kotlin.

Every cycle or heap allocation is very important for meā€¦My generated code user can relax, I cannot.

On C++ or Rust I can create on the stack wrapper and can wrap / cast pointer on the stack to desirable interface and work without worry. But in java you cannot create wrapper on the stack, the heap only. Yes it allocate quite fast.
You will pay lately, with frequent and longer GC cycle.That is measurable on production machine.

Adding inline functions / classes in Kotlin is great. I decides to stop to support pure JAVA ( as i did before, generated code sample ) and switch to Kotlin.

1 Like

I understand the concern.Still there are ways to solve the problem without inventing language constructs. For example, you can create an inline class, capturing the context and passing it to the function. Also in kotlin, unlike java, you can define function right inside another function. And that function will be able to access local variables. If you do not need your inline to be called from more than one place, I think you should go with the later. There are also mostly unexplored possibilities of using scoped extensions like it is done here.

You should remember that in Java small local classes are still allocated on stack due to scalarization. So it is not always obvious how it works, but it is possible to make it quite effective.

Thank you for reply and valuable adviceā€¦
Kotlinā€™s inline functions and lambda make me sure that result code is predictable and working at top performance.
Without surprise.

instead of current

inline fun SomeFun(some_lambda: () -> Unit, get_value: () -> Int, get_value2: () -> Int) {
    some_lambda()
    var value = get_value()
    var value2 = get_value2()
    //value = 45
    //value2 = 450

}

fun main(args: Array<String>) {
    var value: Int = 0
    var value2: Int = 0

    SomeFun({
        value = 45
        value2 = 450
    }, { value }, { value2 })
}

could be better

inline fun SomeFun(some_lambda: () -> Unit) {
    var value: Int = 0
    var value2: Int = 0

    some_lambda()
   
    //value = 45
    //value2 = 450
}


fun main() {
    SomeFun() {
        value = 45
        value2 = 450
    }

I believe itā€™s not big deal to make this code possible.

inline fun SomeFun(some_lambda: () -> Unit, get_value: () -> Int, get_value2: () -> Int, print_sum: (Int) -> Unit) {
    some_lambda()
    var value = get_value()
    var value2 = get_value2()

    //value = 45
    //value2 = 450
    print_sum(value + value2)

}


fun main(args: Array<String>) {
    var value: Int = 0
    var value2: Int = 0

    SomeFun({
        value = 45
        value2 = 450
    }, { value }, { value2 }, { sum -> println(sum) })

    println(value2)
    println(value)
}

Changing inline functions to having dynamic instead of static scoping rules seems like an extremely fundamental change.

The var syntax, if the callsite had var too seems more reasonable to me, not that Iā€™m someone you need to convince. Personally Iā€™d prefer ref rather than var.

While the IDE can help with identifying aspects of the code (type, suspending calls), the ability for a property to be reassigned seems fundamental to understanding what the code is trying to do. Method and property names are often enough to understand code at a basic level even without knowing the type. Same with suspending methods, you know what itā€™s doing without knowing that itā€™s suspending. You can read the code without an IDE. Iā€™m skeptical of that being the case for by reference arguments.

1 Like

You could follow the swift convention and add ā€œinoutā€ parameter keyword. Which might also be useful for kotlin native. The only trouble would be in a performance hit when using them java side, because it would require creating a wrapper value object for each inout parameter.

https://docs.swift.org/swift-book/LanguageGuide/Functions.html#ID173