Pitch: not contains

Filter using keypath notation makes functional programming much more succinct and readable in Swift.

However, there is not an intuitive standard library way to filter by the opposite of the boolean. I previously proposed .toggled or .isFalse and was met with reasonable push back.

How about adding something along the lines of not contains? E.g.

extension Sequence where Element: Equatable {
    func notContains(_ element: Element) -> Bool { !contains(element) }
}

Here is an example use case.

let digitsExceptForFive = [1, 2, 3, 4, 6, 7, 8, 9]
let fives = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter(digitsExceptForFive.notContains)  // [5]
1 Like

What you're doing above isn't keypath notation; it's just passing an instance-bound function to filter. Is a trailing closure that much worse?

let fives = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter { !digitsExceptForFive.contains($0) }

If your goal is succinct, functional programming, then adding special cases for individual APIs won't really get you there. Higher-order functions would be better, which could be applied to anything, not just contains. Something like:

func not<T>(_ predicate: @escaping (T) -> Bool) -> (T) -> Bool {
  { !predicate($0) }
}

let fives = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter(not(digitsExceptForFive.contains))

You can even make it an operator:

prefix func ! <T>(_ predicate: @escaping (T) -> Bool) -> (T) -> Bool {
  { !predicate($0) }
}

let fives = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter(!digitsExceptForFive.contains)

Both of those work with keypaths, too, thanks to the implicit function conversion.

Combinators like these have their purpose, but I imagine some more exploration would be necessary before deciding the best form they should take if someone wanted to aim them at the standard library. There are still some missing pieces in the language that would make them much more complete, like variadic generics.

7 Likes

I'm opposed to this API for the same reasons that I was opposed to toggled/isFalse. The small increase in brevity afforded by this API does not justify adding it to the standard library. You need to come up with a better reason than that. Similar proposals to this one will be met with the same feedback.

12 Likes

I agree with your opinion. A higher order function would be a better, more general solution. A custom operator seems even note expressive.

I fail to see, however, how variadic geberics should hold up such a trivial addition to the standard library. What would potentially cause conflicts between the two implementations?

Variadic generics have been discussed for years. Sure, they’re neat. Other languages have them so l, so Swift should too? This indefinite delay or (surely not—just to play Devil’s advocate) pipedream is a relatively poor excuse to delay a useful functional programmig improvement requiring low effort to implement.

Cheers to the responses so far

Ilias

I think that it's ! so good. The negation customarily belongs at the beginning.

public prefix func !<Root>(
  getBool: @escaping (Root) -> Bool
) -> (Root) -> Bool {
  { !getBool($0) }
}

let digitsExceptForFive = [1, 2, 3, 4, 6, 7, 8, 9]
let fives = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter(!digitsExceptForFive.contains)

While I agree with your operator suggestion, to answer your question directly,

In short, yes, it is that much worse. thus this thread. Having to interject trailing closures impairs readability. of functional code. It negates (ha) some, let's say 1/2 to put a number on it, of the value of introducing keypath notation to form functions.

Thank you for your feedback.