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?