Does Kotlin define function argument evaluation order?


#1

I know C expressly leaves function argument evaluation order undefined. What’s Kotlin’s stance on this?

Example: Where this matters

I’d like to turn this:

data class Thingy(val isOn: Boolean, val size: Int) {
  companion object Parser {
    fun parse(buffer: ByteBuffer): Thingy {
      val isOn = buffer.get() != 0x00.toByte()
      val size = buffer.getInt()
      return Thingy(isOn, size)
    }
  }
}

into this:

data class Thingy(val isOn: Boolean, val size: Int) {
  companion object Parser {
    fun parse(buffer: ByteBuffer) = Thingy(
      isOn = buffer.get() != 0x00.toByte(),
      size = buffer.getInt())
  }
}

But each of those calls is side-effecting on the cursor position of the buffer, so I cannot do so without a guarantee that evaluation order runs left-to-right.


#2

In JVM I believe it’s Left-to-Right as java. Don’t know about native though.
https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.7.4


#3

Thanks. Kotlin/NotJava is what I’m worried about.

I’m looking for a spec at the Kotlin language level rather than at the level of any of its host platforms. Whatever the language spec, the output for a platform could be done to conform, but I need to know what rules apply to Kotlin code on its own terms in order to guide the Kotlin code I write and its assumptions.


#4

Left to right: https://github.com/JetBrains/kotlin-spec/blob/master/kotlin-spec.asc#order-of-evaluation


#5

Perfect, thank you! I’d no luck finding that spec from the Kotlin website or with general Web searches. :woman_facepalming:t4:

I’m not crazy, right - is there really no link from anything under https://kotlinlang.org/docs/reference/ to the language spec? I might be taking advantage of that “edit page” button tomorrow… :dancer:t4:


#6

Your question is answered, but I think that what you are trying to do should not be done in the first place. Relying on order of argument computation is a very bad practice. It saves you one line of code, but creates a lot of complications for debugging and interpretation.


#7

Thank you for your concern @darksnake.

I’m afraid I don’t understand what complications for interpretation you have in mind. Could you provide an example or two?

I do have an idea of the complications inlining the argument expressions could create for a line-oriented debugger. I’ll have to investigate how putting each argument on its own line, but still within the overall constructor call, interacts with line-based breakpoints when using the debugger my team would be using. (Depending on how that goes, I may also see how easy it is to drop down to disassembly and to place a breakpoint there.)


#8

The main example you have provided yourself: one needs to think about evaluation order. Second one, you already mentioned - you can’t easily debug it. There are a lot of problems here. For example, if you try to add new parameters in the future, or refactor the code somewhere and change the order of arguments in another class or interface, it will break your code

I don’t understand, why do you need such complications to save just one line.


#9

maybe it can clarify things

fun x(s:String) = s.also { println(s) }
fun aFunction(a:String,b:String){}

fun main(args: Array<String>) {
    val x = Test()
    aFunction(x("a"), x("b")) //prints a b
    aFunction(a= x("a"), b=x("b")) // prints a b
    aFunction(b=x("b"),a= x("a")) //print b a
}

#10

That’s a good question. For just one line, it would not be worth it. But here, I am trying to reduce one degree of obnoxious duplication. I’m dealing with data classes that are fundamentally mirrors of a binary protocol specification, and I find myself writing the same information out three times:

  • As fields on the data class

  • As fields to pack into a ByteBuffer

  • As fields to read out of a ByteBuffer

I’m no more concerned with parameter order changing or new fields being added than I am that TCP might decide to relocate where its sequence number is stored. (Poor folks can’t even use their reserved bytes for anything. For shame, routers, for shame.) Besides, I’m using named parameters, so order changes are fine; arity changes not so fine, but see again that bit about a binary protocol, and add in some roundtripping regression tests for added security.

So the deeper question is: What’s a good way to generate two of those duplicates given one of them? (Most likely, given the data class constructor decl.)

If Kotlin had Lisp- or Scala-like macros, I’d reach for those. Is the closest alternative doing something to wire into kapt and do codegen driven by an annotation?


#11

You can take a look at kapt and annotation processing. That way you should be able to generate both read and write to byte buffers. The only problem is that you can’t modify existing sources so you can not have them be part of the original class. That should however be fine as long as all your values are public (which they probably are when you are using a data class).