Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to implement an applyif for Kotlin?

Tags:

kotlin

I'd like to have an applyif to work like:

builder.applyif(<condition expression>) {
    builder.set...
}

to be equal with:

builder.apply {
    if (<condition expression>) {
        builder.set...
    }
}

Is that possible?

like image 396
Elderry Avatar asked Jul 31 '18 06:07

Elderry


2 Answers

Yes, of course. You can nearly program anything, but don't reinvent the wheel. Look at the bottom of the answer to see a standard Kotlin approach without own extension function(s) which may already suffice your needs (not exactly applyIf though).

Now, however, lets see how an applyIf might be implemented:

inline fun <T> T.applyIf(predicate: T.() -> Boolean, block: T.() -> Unit): T = apply { 
  if (predicate(this)) 
    block(this) 
}

Don't forget the inline if you are implementing extension functions with lambdas.

Here is an example usage of the above.

// sample class
class ADemo {
  fun isTrue() = true
}

// sample usage using method references
ADemo().applyIf(ADemo::isTrue, ::println)

// or if you prefer or require it, here without
ADemo().applyIf( { isTrue() } ) {
  println(this)
}

If you just want to supply a boolean instead, you can use the following extension function:

inline fun <T> T.applyIf(condition : Boolean, block : T.() -> Unit) : T = apply { 
  if(condition) block(this) 
}

and call it with:

val someCondition = true
ADemo().applyIf(someCondition) {
  println(this)
}

And now a possible Kotlin standard way with which more people could be familiar:

ADemo().takeIf(ADemo::isTrue)
       ?.apply(::println)

// or
ADemo().takeIf { it.isTrue() }
       ?.apply { println(this) }

If they do remember (I actually didn't until I saw Marko Topolniks comment) they should immediately know what's going on. However, if you require the given value (i.e. ADemo()) after calling takeIf this approach might not work for you as the following will set the variable to null then:

val x = ADemo().takeIf { false }
               ?.apply { println(this) /* never called */ }
// now x = null

whereas the following will rather set the variable to the ADemo-instance:

val x = ADemo().applyIf(false) { println(this) /* also not called */ }
// now x contains the ADemo()-instance

Chaining the builder calls might not be so nice then. Still you can also accomplish this via standard Kotlin functions by combining the takeIf with apply or also (or with, let, run, depending on whether you want to return something or not or you prefer working with it or this):

val x = builder.apply {
  takeIf { false }
    ?.apply(::println) // not called
  takeIf { true }
    ?.apply(::println) // called
}
// x contains the builder

But then again we are nearly there where you were already in your question. The same definitely looks better with applyIf-usage:

val x = builder.applyIf(false, ::println) // not called
               .applyIf(true) { 
                 println(this) // called
               }
// x contains the builder
like image 161
Roland Avatar answered Oct 11 '22 07:10

Roland


Sure you can, you just need an extension function so you can call it on the builder, and you need it to take a Boolean parameter and the lambda to execute.

If you look at the source of the apply function itself, it will help with most of the implementation:

public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

Based on this, applyIf can be as simple as:

inline fun <T> T.applyIf(condition: Boolean, block: T.() -> Unit): T {
    return if (condition) this.apply(block) else this
}

Usage looks like this:

builder.applyIf(x > 200) {
    setSomething()
}
like image 2
zsmb13 Avatar answered Oct 11 '22 06:10

zsmb13