Which code style would you prefer?


#1

Formerly I would refactor codes from nested parentheses

val obj = SomeObject()
actionThree(actionTwo(actionOne(obj)))

to chained .let statements

val obj = SomeObject()
obj.let {actionOne(it)}
    .let {actionTwo(it)}
    .let {actionThree(it)}

Which one would you prefer?

P.S. I’d like to know why the newest kotlin plugin issue a warning in code inspect on the last code style.


#2

I would prefer the first approach with nested calls, because the actual control flow (what result is passed where) is much better visible. If readability is your goal, I would introduce variables with a meaning:

val average = actionOne(obj)
val power = actionTwo(average)
val cost = actionThree(power)

This would add a meaning and not just superficial cosmetics.


#3

The relative topic is pipe forward operator Pipe-forward operator |>

I think the second approach is much readability.


#4

Users with the desire for |> can (and should!) switch to Scala or F# :wink: The ugliness of this operator is outstanding!

There is a simple solution with standard Kotlin language features.


#5

Just wanted to point out that you can use function references and drop the lambdas and the it. The syntax for the function references can vary based on where the function is defined. In the simplest case that would be:

val obj = SomeObject()
obj.let(::actionOne)
    .let(::actionTwo)
    .let(::actionThree)

#6

That’s not always true and one of the big problems with Kotlin function reference usage/resolution. If those functions had default params for instance, it breaks. There’s no mention of the function signature so we shouldn’t assume.


#7

Probably not too relevant to all this pipe forward, functional programming discussion that keeps happening, but when I think of function composition mathematically, I think of:
f(g(h(x))) = (f ∘ g ∘ h)(x)

I don’t know what syntax would best encapsulate this kind of mathamatical formatting, but it’s worth putting out there.


#8

Well if you want you could do something like this

inline infix fun <reified A, reified B, reified C> ((B)->C).compose(crossinline other: (A) -> B): (A) -> C 
    = { x -> this(other(x)) }

you can use it like this

(::f compose ::g compose ::h)(x) == f(g(h(x)))

#9

The Funktionale library has such operators so that (if I got the difference between compose and forwardCompose correct):

(::f compose ::g compose ::h)(x) == f(g(h(x)))
(::h forwardCompose ::g forwardCompose ::f)(x) == f(g(h(x)))


#10

When did I say it was always true? The OP showed 2 alternatives and I simply showed that there is a third alternative using function references. The ability to use function references is a point in the favor of the let alternative and why I prefer it.

That style also works well if for example actionTwo was a member or extension function instead of a function that took a parameter. You can stay in that style and do:

val obj = SomeObject()
obj.let(::actionOne)
    .run(SomeType::actionTwo)
    .let(::actionThree)

#11

Just saying, you can’t go running around telling people the lambda can be replaced with a function reference without knowing the function signature just like the IntelliJ inspection limitations (and many times it won’t tell you it can be replaced because the signature doesn’t allow it).


#12

But it can except for some rare circumstances. It is rare enough that I have never actually ran into cases where it couldn’t.