Why do I get different boolean logic from "and" vs "&&"?

In the following unit-test, the 2nd test fails. But the only difference is that one uses and for its boolean operator and the other uses &&.

My understanding is that those two operators are logically the same (but that and does not short-circuit).

import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class BooleanAndComparisonTest {
    data class CustomerProfile(val deleted: Boolean, val mobileVerified: Boolean)

    @Test
    fun testUsingDoubleAmpersand() {
        assertTrue { shouldCustomerProfileBeFilteredOut_usingDoubleAmpersand(CustomerProfile(deleted = false, mobileVerified = true)) }
        assertFalse { shouldCustomerProfileBeFilteredOut_usingDoubleAmpersand(CustomerProfile(deleted = false, mobileVerified = false)) }
        assertFalse { shouldCustomerProfileBeFilteredOut_usingDoubleAmpersand(CustomerProfile(deleted = true, mobileVerified = true)) }
        assertFalse { shouldCustomerProfileBeFilteredOut_usingDoubleAmpersand(CustomerProfile(deleted = true, mobileVerified = false)) }
    }

    @Test
    fun testUsingAnd() {
        assertTrue { shouldCustomerProfileBeFilteredOut_usingAnd(CustomerProfile(deleted = false, mobileVerified = true)) }
        assertFalse { shouldCustomerProfileBeFilteredOut_usingAnd(CustomerProfile(deleted = false, mobileVerified = false)) }
        assertFalse { shouldCustomerProfileBeFilteredOut_usingAnd(CustomerProfile(deleted = true, mobileVerified = true)) }
        assertFalse { shouldCustomerProfileBeFilteredOut_usingAnd(CustomerProfile(deleted = true, mobileVerified = false)) }
    }

    private fun shouldCustomerProfileBeFilteredOut_usingAnd(customerProfile: CustomerProfile): Boolean {
        return (customerProfile.deleted == false and customerProfile.mobileVerified == true)
    }

    private fun shouldCustomerProfileBeFilteredOut_usingDoubleAmpersand(customerProfile: CustomerProfile): Boolean {
        return (customerProfile.deleted == false && customerProfile.mobileVerified == true)
    }
}

Here’s the version of kotlin we are running:

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10"
    }

I could be wrong but it looks like and in this example is called with argument false and argument customerProfile.mobileVerified.

1 Like

Wow this is a weird one… possible language bug?

The second test is failing here:

assertFalse { shouldCustomerProfileBeFilteredOut_usingAnd(CustomerProfile(deleted = false, mobileVerified = false)) }

If you evaluate the conditions, here’s what you get:

customerProfile.deleted == false // true
customerProfile.mobileVerified == true // false
customerProfile.deleted == false and customerProfile.mobileVerified == true // true - wait what?????

The left expression evaluates to true, the right expression evaluates to false, Kotlin takes the true and the false, performs a logical and operation according to the comments, and… comes out with a true??? What in the hippity hoppity heck???

I’m running Kotlin 1.9.0, and this is the JavaDoc for the Boolean and function:

    /**
     * Performs a logical `and` operation between this Boolean and the [other] one. Unlike the `&&` operator,
     * this function does not perform short-circuit evaluation. Both `this` and [other] will always be evaluated.
     */

@MikibeMiki is right. The difference is in precedence. Using parentheses like this gives the expected result:

    private fun shouldCustomerProfileBeFilteredOut_usingAnd(customerProfile: CustomerProfile): Boolean {
        return (customerProfile.deleted == false) and (customerProfile.mobileVerified == true)
    }

However, simplifying to the following both gives the expected result and is cleaner:

return !customerProfile.deleted and customerProfile.mobileVerified
return !customerProfile.deleted && customerProfile.mobileVerified
3 Likes
customerProfile.deleted == false and customerProfile.mobileVerified == true

is equivalent to

customerProfile.deleted == (false and customerProfile.mobileVerified) == true

which in this case evaluates to:

false == (false and false) == true // -->
false == false == true // -->
(false == false) == true // -->
true == true // -->
true

The reason is that infix functions (and in this case) has higher precedence than ==.
Grammar - Expressions

2 Likes

Nice, good catch… I didn’t even think of that.

1 Like

Thank you all for such quick and thorough responses! Makes complete sense.

In our case, it turns out we didn’t need the “non-short-circuiting” behavior of the and so we just switched to the && operator.

I would like to suggest that a note be added to the kotlin documentation for this infix function. Something along the lines of:

“Note: infix functions such as and have a higher precedence than their corresponding non-infix counterparts (in this case &&) as well as having a higher precedence than the == comparison operator. This can lead to surprising results when evaluating boolean expressions”

Is there a way to contribute that documentation-addition to the Kotlin project? Like maybe just open a PR somewhere?