What is the Kotlin style when writing nested logical operations over multiple lines?


#1

How am I supposed to write logical operations over multiple lines?

The reason for the question has to do with IntelliJ Kotlin-code-formatter:
The IntelliJ Kotlin-code-formatter has been great except for when dealing with nested logical operations over multiple lines. I want the code to be aligned, and the formatter doesn’t do that. It forcefully un-align even.

I get this:

                    BE.AEG[P].AI_AKTBORS[1] >= 0.0
                && BE.AEG[P].AI_AKTUNOT[1] >= 0.0
                && KL.AEG[P].F849 == 0.0
                && KL.AEG[P].F890 == 0.0
                && (
                        KL.AEG[P].F782 == 0
                                || BE.AEG[P].F329 == 0.0
                        )

Something more pleasing to the eyes could be this:

                      BE.AEG[P].AI_AKTBORS[1] >= 0.0
                && BE.AEG[P].AI_AKTUNOT[1] >= 0.0
                && KL.AEG[P].F849 == 0.0
                && KL.AEG[P].F890 == 0.0
                && (
                          KL.AEG[P].F782 == 0
                       || BE.AEG[P].F329 == 0.0
                      )

I have tried all kinds of ways to rearrange the code, so the formatter might not make an unaligned solution but to no avail. - Having everything behind parenthesis on one line works, but that’s not what I want.

Since Jetbrains is the creator of both Kotlin and IntelliJ’s Kotlin-code-formatter I would expect the formatter to reflect Jetbrains’ ideas on how Kotlin’s code should be formatted, and I doubt this was the idea:
KL.AEG[P].F782 == 0
|| BE.AEG[P].F329 == 0.0


#2

quote using

three
  reverse
    bracket
` ` `

And try to put infix operator before the carriage return

BE.AEG[P].AI_AKTBORS[1] >= 0.0 &&
 BE.AEG[P].AI_AKTUNOT[1] >= 0.0

#3

I had tried a lot of things including that, and it still doesn’t work.

                BE.AEG[P].AI_AKTBORS[1] >= 0.0
                && BE.AEG[P].AI_AKTUNOT[1] >= 0.0
                && KL.AEG[P].F849 == 0.0
                && KL.AEG[P].F890 == 0.0
                && (KL.AEG[P].F782 == 0 ||
                        BE.AEG[P].F329 == 0.0
                        )

#4

Sorry to get sidetracked, but I hate an operator at the end of a line! Even if your window is wide enough to show it (which isn’t a given), it still takes a lot longer to see the big picture: operators don’t line up, and you need to take in the whole thing before you can make sense of it. Whereas if the operators are at the start of a line, you can get the structure just from glancing at the LHS.

That’s why I think Kotlin’s line-breaking is too eager. IMHO it should assume a semicolon only if it’s necessary to make sense of the line.

Most of the time the ambiguity gives errors, but occasionally it changes the meaning of valid code, e.g.

    val a = (somevariable * someothervariable)
          - (anothervariable * yetanothervariable)

In which, thanks go the magic of the unary minus, the second line is silently ignored!

I can’t imagine a case where two lines could form a single statement, but you wouldn’t want them to.

(So, getting back to the point: @TorRanfelt, I’d encourage you to keep on splitting lines where you do, however much the formatter tries to sabotage it! :smile:)


#5

@gidds I completely agree.
Also, whether I have I have the operator at the beginning or at the end of the lines the formatter makes inconsistent indentation. :frowning: So there is no reason to to move them to the back.

@fvasco The problem happens when you mix parenthesis into the expression.


#6

@fvasco was suggesting with “try to put the infix operator before the carriage return” to write something like:

val mumble =
        BE.AEG[P].AI_AKTBORS[1] >= 0.0 &&
        BE.AEG[P].AI_AKTUNOT[1] >= 0.0 &&
        KL.AEG[P].F849 == 0.0 &&
        KL.AEG[P].F890 == 0.0 &&
        (KL.AEG[P].F782 == 0 ||
                BE.AEG[P].F329 == 0.0)

I pushed everything onto one line and then let Android Studio have a whirl at formatting. It did leave the infix at end of line, but chose to wrap before the comparator rather than the Boolean operator, which no human would ever do:

val mumble =
    BE.AEG[P].AI_AKTBORS[1] >=
    0.0 && BE.AEG[P].AI_AKTUNOT[1] >=
    0.0 && KL.AEG[P].F849 ==
    0.0 && KL.AEG[P].F890 == 0.0 &&
    (KL.AEG[P].F782 == 0 || BE.AEG[P].F329 == 0.0)

Considering how many magic numbers and cryptic abbreviations are in here, you might well benefit from extracting explaining variables or private properties for the various predicates, and then chaining those together. If short-circuit evaluation is unnecessary, you could even do something like:

val mumble =
    listOf(
            predicate1,
            predicate2,
            listOf(
                    predicate3,
                    predicate4)
                    .any { it })
            .all { it }

This has the advantage of pushing the operators any and all to the left of the line, as well, as @gidds would prefer.