Negating predicates (difficulties with escaping and rethrowing)

When writing an algorithm that uses a predicate function, it is often necessary to pass the negated version of that predicate to another function. This occurs both in the standard library and user code.

The way to achieve this today is, eg:

let i = try myCollection.firstIndex{ try !predicate($0) }

That works, but for simplicity and clarity I’d rather write:

let i = try myCollection.firstIndex(where: !predicate)

So I made a helper function to negate a boolean predicate function:

prefix func ! <T> (_ f: @escaping (T) throws -> Bool) -> (T) throws -> Bool {
  return { try !f($0) }
}

This works, but the problem is that it requires f to be marked as @escaping, which means the predicate in the calling code must itself be @escaping, and this propagates virally.

I’m aware of the withoutActuallyEscaping function, but I’m hesitant to use it here since f really does escape the negation function.

Furthermore, even if I deal with the escaping-ness, when I try to use this in practice I run into issues with rethrows:

extension Collection {
  func firstIndexWhereNot(_ predicate: @escaping (Element) throws -> Bool) rethrows -> Index? {
    // error: a function declared 'rethrows' may only throw if its parameter does
    return try firstIndex(where: !predicate)
  }
}

Of course, we reading the code understand that !predicate can only rethrow errors from predicate, but the compiler doesn’t know that.

So, I’m wondering if there’s a proper way to do this.

I recognize that the actual syntactic change at the point of use is quite small. But for me personally, code written with !predicate is much easier to read, and its meaning is clear at a glance.

Achieving this seemed so simple on the surface: I have a closure that returns a Bool, and I want a new closure that returns its negation.

But when I try to actually make it work, I keep running into roadblocks.

2 Likes