Ternary operator workaround

Hello community! i want to share my implementation of the ternary operator hoping a constructive feedback to improve mi kotlin skills. see ya!

I use annotations to make the decision

@Target(AnnotationTarget.CLASS)
@Retention(RUNTIME)
annotation class ConditionAnnotation(val condition: Boolean = false)

@ConditionAnnotation(true)
class IfCondition

@ConditionAnnotation(false)
class ElseCondition

the inline functions then an orelse respone are selected depending of the condition
i must to take the dessision of check for previous annotations beacuse of the kotlin optimizations that returns the same instance for the lambda function paramters. I am thinking in crate those with an object instantiation

inline infix fun <reified T : Any> Boolean.then(ifResponse: T): T {
    val annotations = ifResponse::class.annotations as MutableList


    annotations.removeAll(annotations.filterIsInstance<ConditionAnnotation>())

    annotations.add(
        when (this) {
            true ->
                IfCondition()::class.java.getAnnotation(ConditionAnnotation::class.java)
            false ->
                ElseCondition()::class.java.getAnnotation(ConditionAnnotation::class.java)
        }
    )

    return ifResponse
}

inline infix fun <reified T : Any> T.orElse(elseResponse: T): T {

    return this::class.findAnnotation<ConditionAnnotation>()?.let {
        if (!it.condition) elseResponse else this
    } ?: this

}

inline infix fun <reified T : Any> T.orElse(elseResponse: T): T {

    return this::class.findAnnotation<ConditionAnnotation>()?.let {
        if (!it.condition) elseResponse else this
    } ?: this

}

this is how the test looks

@Test
fun testTernary() {
    then(true, "true", "false", "true")
    then(true, "true", "false", "true")
    then(false, "true", "false", "false")

    then(true, 1, 0, 1)
    then(false, 1, 0, 0)

    then(true, MEDIATYPE.MOVIE, MEDIATYPE.TV, MEDIATYPE.MOVIE)
    then(false, MEDIATYPE.MOVIE, MEDIATYPE.TV, MEDIATYPE.TV)
}

private inline fun <reified T : Any> then(condition: Boolean, ifCondition: T, elseCondition: T, expectedValue: T) {

    Assert.assertEquals(condition.then(ifCondition).orElse(elseCondition), expectedValue)
    
    //infix
    Assert.assertEquals(condition then ifCondition orElse elseCondition, expectedValue)
}

i hope that someone finds this usseful

A much more elegant approach

//IF
infix fun <T> Boolean.then(ifResult: T?): Pair<Boolean, T?> {
    return Pair(this, ifResult)
}

//ELSE
infix fun <T> Pair<Boolean,T?>.orElse(elseResult: T?): T? {
    return if( this.first){
        this.second
    }else{
        elseResult
    }

Here are the tests

@Test
fun testTrue() {
    val str = "false"
    thenOrElse(true, null, str, null)
    thenOrElse(true, "true", str, "true")
    thenOrElse(true, "true", str, "true")

    val int = 0
    thenOrElse(true, 1, int, 1)

    val nullable: String? = null
    thenOrElse(true, "true", nullable, "true")
}


@Test
fun testFalse() {
    val str = "false"
    thenOrElse(false, null, str, str)
    thenOrElse(false, "true", str, str)
    val int = 0
    thenOrElse(false, 1, int, int)

    val nullable: String? = null
    thenOrElse(false, "true", nullable, nullable)
}
private fun <CLASS> thenOrElse(condition: Boolean, ifResult: CLASS?, elseResult: CLASS?, expectedValue: CLASS?) {
    assertEquals(condition.then(ifResult).orElse(elseResult), expectedValue)
    assertEquals(condition then ifResult orElse elseResult, expectedValue)
}

If I may correct what seems to be to be a common misunderstanding: Kotlin has a ternary operator!

In fact, since any operator which takes three operands is a ternary operator, let me narrow it down and say that Kotlin has the same ternary operator as Java.

It’s just that the syntax is a little different.

Java:

a ? b : c

Kotlin:

if (a) b else c

It’s a little more long-winded than Java, but it works exactly the same way.

Most people don’t think of it as an operator, because they’re used to languages like C and Java where the if construction is a statement. But in Kotlin it’s an expression, and yields a value (even if that value is just Unit).

So there’s no need for another.

Of course, you may not want to use it on æsthetic grounds, and prefer something that looks more like other operators. But this seems like an awful lot of trouble to avoid a built-in language construct.

It’s true that it’s less concise than the Java one, and doesn’t look like most other operators. But it does read better in many cases. And it more clearly separates the condition (a) from the two possible result values (b and c). And there’s a theoretical elegance from unifying it with the if statements, merging two constructions that belong together.

OK, end of rant!

As for your particular implementations, I haven’t had time to study them carefully; I hope someone else will give them a better critique. But both ring alarm bells regarding performance — something which is pretty important for such a fundamental construct.

The first one uses reflection, which does not perform well. My view is that reflection is a great tool for frameworks, build tools, plug-ins, and other situations where you need to work with external code that may not be available yet — but you almost never need it for code within the same module, and using it like that seems like a very strong code smell.

The second one avoids that, but creates temporary objects, which can also have serious performance implications for such a basic construct.

However, both suffer from an even more important problem: they evaluate both results every time. In Java’s ? : operator and both languages’ if construction, only one result gets evaluated. If I write:

if (someCondition)
    someExpensiveCalculation()
else
    someOtherExpensiveCalculation()

Then only one of the two calculations will be performed, regardless of the condition. Whereas, in both your implementations (if I follow them correctly) both calculations would get performed each time, and then the results of one used and the other discarded.

That’s not just a performance issue; it’s a semantics one. (As either or both of the results could involve side-effects.)

You could probably work around that by taking two lambdas instead of two values; but then the syntax would suffer (and it would involve more temporary objects, unless it was all inlined).

So neither of these implementations is ready for prime-time, I’m afraid.

Still, it’s fun to play around with the language and see what can be achieved; Kotlin does lend itself to some ingenious constructions. And you’ve certainly come up with a couple of interesting approaches! So if your objective was to learn some Kotlin and get creative with it, then you seem to have succeeded :slight_smile:

7 Likes

Just use the language you are programming in! I find the solution overly complicated and hard to read - with no benefit. It is ok to do things for fun and to improve the own skills, but theses things are not necessarily useful in general :wink: if/else as an expression is perfectly fine, easy to read, fast, well tested, perfectly documented, well understood.