what if somewhere else in the code
Then you'd need to do something else.
the semantical and resourcewise equivalent would be a third variable
So you are advocating for:
data class Filters(
val isAdmin: Boolean = false,
val filterByAdmin: Boolean = false,
val isConfirmed: Boolean = false,
val filterByConfirmed: Boolean = false,
)
fun filterUsers(users: List<User>, filters: Filters): List<User> {
return users
.filter { !filters.filterByAdmin || it.isAdmin == filters.isAdmin }
.filter { !filters.filterByConfirmed || it.isConfirmed == filters.isConfirmed }
}
fun getAdmins() {
val users = getUsers()
val filters = Filters(isAdmin = true, filterByAdmin = true)
val admins = filterUsers(users, filters)
println("Admins: $admins")
}
Over:
data class Filters(
val isAdmin: Boolean? = null,
val isConfirmed: Boolean? = null,
)
fun filterUsers(users: List<User>, filters: Filters): List<User> {
return users
.filter { filters.isAdmin == null || it.isAdmin == filters.isAdmin }
.filter { filters.isConfirmed == null || it.isConfirmed == filters.isConfirmed }
}
fun getAdmins() {
val users = getUsers()
val filters = Filters(isAdmin = true)
val admins = filterUsers(users, filters)
println("Admins: $admins")
}
To me, Filters(isAdmin = true) is a very easy to use API, where Filters(isAdmin = true, filterByAdmin = true), with the additional variable to avoid nullable booleans, is more verbose, for not much benefit and brings ambiguity. What if someone writes Filters(isAdmin = true), but forgets they need to set filterByAdmin = true for it to actually work? Easy mistake to make. We can prevent these mistakes by removing default values so they have to be specified on the call site, but then you need Filters(isAdmin = true, filterByAdmin = true, isConfirmed = false, filterByConfirmed = false), which is very verbose. Having two variables also allows your systems to get into invalid states:
isAdmin = true, adminRightsHaveBeenValidated = false
isAdmin = true, filterByAdmin = false
What do these mean? It's better for invalid states to be unrepresentable. Since these states are mutually exclusive, we should have only 3 states, not 4 which you get with 2 booleans. Which you could achieve with an enum True, False, None, but then you are just reinventing the wheel of nulls. You also get the issue that now you have to remember to always update both variables together.
It all comes back to your point:
it’d be idiomatic and reasonable to assume it to be false if we have no data
You want to have ambiguous states, where a single value represents both "we have no data" and "we have the data, the answer is no".