My approach at that time was to have two functions, one using the not
-operator and the other being a simple not
-function accepting a predicate. Today I can't really recommend that approach anymore, but would rather choose the following instead, if I have to deal with many predicate negations for keys or values again:
inline fun <K, V> Map<out K, V>.filterKeysNot(predicate: (K) -> Boolean) = filterKeys { !predicate(it) }
inline fun <K, V> Map<out K, V>.filterValuesNot(predicate: (V) -> Boolean) = filterValues { !predicate(it) }
That way a given predicate can simply be used by just calling filterKeysNot(givenPredicate)
similar to what was already possible with filterNot
on collections.
For the problem I had at that time I was able to do a refactoring so that the data could be partitioned appropriately and therefore the predicate negation wasn't needed anymore.
If I only need it in rare occasions I would rather stick to filterKeys { !predicate(it) }
or filterNot { (key, _) -> predicate(key) }
.
The following variants show how something like Predicates.not
or Predicate.negate
could be implemented:
The following will allow to use the !
-operator to negate a predicate (if several parameters should be allowed an appropriate overload is required):
operator fun <T> ((T) -> Boolean).not() = { e : T -> !this(e) }
The next allows to use not( { /* a predicate */ } )
. This however, at least for me, isn't really more readable:
inline fun <T> not(crossinline predicate: (T) -> Boolean) = { e : T -> !predicate(e)}
Usages:
val matchingHello : (String) -> Boolean = { it == "hello" }
mapOf("hello" to "world", "hi" to "everyone")
.filterKeys(!matchingHello)
// or .filterKeys(not(matchingHello))
// or .filterKeys(matchingHello.not())
// or as shown above:
// .filterKeysNot(matchingHello)
.forEach(::println)