Add apply extension function that accepts a parameter


#1

The apply function is such a vital and game changing feature over Java, but I sometimes find it would be great if there was a version that also accepted a parameter defined as:

inline fun <T, P> T.apply(p: P, f: T.(P) -> Unit): T { f(p); return this }

The primary case for this is cases where I want to use apply inside an extension function and the body of the apply needs to access the “this” of the extension function. The only way to handle this now is with qualified this, but that is kinda ugly and less obvious:

fun Bar.toFoo() = Foo().apply { initializeWithBar(this@toFoo) }

With this change you could call it as:

fun Bar.toFoo() = Foo().apply(this) { initializeWithBar(it) }

Another use case would be where you need to construct a new instance of something for use multiple times in the apply method. You will likely create a val just to give a name for it as in:

fun makeFoo() : Foo
{
    val bar = Bar(5)

    return Foo().apply { useBar(bar); alsoUseBar(bar) }
}

With this change that could become:

fun makeFoo() = Foo().apply(Bar(5)) { useBar(it); alsoUseBar(it) }

Since this is an inline function it has no runtime cost and would simplify some uses of apply and should be added to the standard library

It may be the case that there should also be versions of run that should also add parameterized versions


Spreadsheet for differences between let, with, run, apply and also
#2

I should probably point out that there is another way to do these by adding let which basically converts this to a parameter:

fun Bar.toFoo() = let { Foo().apply { initializeWithBar(it) } }

and

fun makeFoo() = Bar(5).let { Foo().apply { useBar(it); alsoUseBar(it) } }

But seems it would be clearer to do it all in one construct and personally I find let to be very unintuitive.


#3

The last example can also be written as:

fun makeFoo() = Foo().apply { Bar(5).let { useBar(it); alsoUseBar(it) } }

While we’re about to introduce tap function (KT-6903) that doesn’t shadow this of an outer block, your proposal seems interesting, we should definitely consider it. I’ll link this topic to the issue.

With the tap function (name is to be considered) you could initialize Foo with Bar without labeling this:

fun Bar.toFoo() = Foo().tap { it.initializeWithBar(this) }

#4

Yay for tap. I already have it in my Utils file, as:

fun <T> T.after(f: (T) -> Unit): T { f(this) ; return this }

after because thing.after { doStuffWith(it) }: you return the thing after doing stuff with (or to) it.

But I must agree with Dale, nesting lets/applys/… gets ugly very quickly, so I’m in favor of the apply overload.


#5

Tap, looks great (although I hate the name), but I think the parameterized apply makes sens too.

I also think adding a parameter for the run functions also makes sense, but perhaps it might be better for readability to name the parameterized versions applyWith and runWith.