5

I have a chain of predicate clauses, something like this

student?.firstName?.equals("John") ?: false &&
student?.lastName?.equals("Smith") ?: false &&
student?.age?.equals(20) ?: false &&
student?.homeAddress?.equals("45 Boot Terrace") ?: false &&
student?.cellPhone?.startsWith("123456") ?: false

I have found that instead of && it's possible to switch to Boolean predicate and(), but overall it doesn't make code more concise.

Is there is a way in Kotlin to simplify such expression?

  • 1
    `student?.run { firstName("John") && lastName("Smith") .. }` https://kotlinlang.org/docs/reference/scope-functions.html#run – JB Nizet Nov 18 '19 at 12:23
  • it will not compile because of nullable types. Expression **&&** only works with safe non nullable types – kirill leonov Nov 18 '19 at 12:29
  • 3
    `student?.firstName?.equals("John") ?: false` could be simplified to `student?.firstName == "John"`, &c.  (Because `==` handles nulls.) – gidds Nov 18 '19 at 12:35
  • instead of writing `?: false` you can also just write `== true`... – Roland Nov 18 '19 at 15:05
  • is it a better practice to use comparison instead of elvis operator? I see the difference between those two operators `?:` _is same as if else_, wherease `==` _is only comparison_ – kirill leonov Nov 18 '19 at 15:22
  • IMO elvis is clearer than `== true` in this situation. – Tenfour04 Nov 18 '19 at 15:23
  • 1
    If it is a data class and if you're comparing all properties, I would suggest to create student object with expected values, then just compare two data class objects like (yourStudent == expectedStudent). – Jegan Babu Nov 18 '19 at 15:44
  • just because you can doesn't mean you have to ;-) hmmm... I just saw that Intellij has its own inspection telling you that you could replace that `?: false` to `== true`... I don't mind really... I find both variants not so nice... I don't even mind writing `if (student != null && student.run { lastName == ..... })` in this case... maybe would even prefer it in this case... – Roland Nov 18 '19 at 15:49
  • @Rolan, Intellij suggests both converting `?:` to `==` and vice versa if you navigate to tips :) Personally, I coded in Java a lot and found writing boilerplate code over and over again. So I would like to replace if null checks wherever it's possible with function calls and I think this is a good place to start from. – kirill leonov Nov 18 '19 at 16:03
  • @kirillleonov interesting... but also strange... one is implemented as inspection (and is therefore marked accordingly in the editor) and the other is an intention, which one can use, but which can not be marked as error/warning/info or whatever... doesn't seem congruent to me... – Roland Nov 18 '19 at 16:17

3 Answers3

4

Thanks everyone who participated! Here is a final version of the code with notes:

student?.run {
  firstName == "John" &&
  lastName == "Smith" &&
  age == 20 &&
  homeAddress == "45 Boot Terrace" &&
  cellPhone.orEmpty().startsWith("123456")
} ?: false
  1. Scope function run {} is called on an object student
  2. equals is replaced by == to compare boolean as well as null values
  3. return type of scope function is nullable, so elvis operator is used ?: false. Another option is to use == true, but it's your personal preference
0

For example

val result = listOf(
    student.firstName == "John",
    student.lastName == "Smith",
    student.age == 20,
    student.cellPhone.orEmpty().startsWith("123456")
).all { it }

or

fun isAllTrue(first: Boolean, vararg other: Boolean): Boolean {
    return first && other.all { it }
}

val result = isAllTrue(
    student.firstName == "John",
    student.lastName == "Smith",
    student.age == 20,
    student.cellPhone.orEmpty().startsWith("123456")
)

or

fun Iterable<Boolean>.isAllTrue(): Boolean {
    return all { it }
}

val result = listOf(
    student.firstName == "John",
    student.lastName == "Smith",
    student.age == 20,
    student.cellPhone.orEmpty().startsWith("123456")
).isAllTrue()
  • please revise your answer... you forgot to handle the nullable-type-issue... – Roland Nov 18 '19 at 15:46
  • Are you talking about the string "student.firstName ==" John "" where the `firstName` parameter is used as `String?`? – Stanley Wintergreen Nov 18 '19 at 17:25
  • From the question we see that `student` can be `null` already... so I rather meant `student?.firstName`, etc... Note also that with your current approach you lose the short-circuiting and unnecessarily create a list for each such condition... – Roland Nov 18 '19 at 22:09
  • I'd go for the first one but instead of listOf i'd go for setOf because it's faster than a list. – M'aiq the Coder Jan 21 '21 at 14:57
0

Not exactly what OP wants, but it seems like the issue here is comparing between two objects of type Student rather than chaining predicates.

Not sure what the use case is but here's a more object-oriented solution, where we park the predicates under Student::isSimilarToJohn (because I'm assuming this John Smith is pretty special):

data class Student(
    val firstName: String?,
    val lastName: String?,
    val age: Int?,
    val homeAddress: String?,
    val cellPhone: String?,
) {
    fun isSimilarToJohn(): Boolean {
        return firstName == "John" &&
            lastName == "Smith" &&
            age == 20 &&
            homeAddress == "45 Boot Terrace" &&
            cellPhone.orEmpty().startsWith("123456")
    }
}

Example:

val students = listOf(
    Student("John", "Smith", 20, "45 Boot Terrace", "1234568"),
    Student("John", "Smith", 20, "45 Boot Terrace", "1234567"),
    Student("Mary", "Smith", 20, "45 Boot Terrace", "1234567"),
    Student("John", "Doe",   20, "45 Boot Terrace", "1234567"),
)
students.map { it.isSimilarToJohn() }
// [true, true, false, false]
remykarem
  • 2,251
  • 22
  • 28