Swift: use not operator functionally

258 views Asked by At

I would like to get the opposite of a boolean value using dot syntax.

E.g.

let digitsExceptForFive = [1, 2, 3, 4, 6, 7, 8, 9]
let fives = [1, 2, 3, 4, 5, 6, 7, 8, 9].filter(!digitsExceptForFive.contains)
// Cannot convert value of type '(Int) -> Bool' to expected argument type 'Bool'

The following code works, however.

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

I need to get the opposite value functionally using the not operator. If it's possible I would like not to have to define a bool extension or custom operator just to do so.

How can you do this in Swift?

3

There are 3 answers

4
Leo Dabus On BEST ANSWER

I would just extend Sequence protocol constraining Element to Equatable and implement a notContains method:

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

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]

You can also extend Bool and provide a negated property, this would allow to use negated KeyPath syntax:

extension Bool {
    var negated: Bool { !self }
}

let string = "abc12345" 
let nonDigits = string.filter(\.isWholeNumber.negated) // "abc"

if you really want to implement a custom prefix operator (I really prefer using the notConstains method and the Bool negated instance property) you can do something like:

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

Usage:

let string = "abc12345"
let nonDigits = string.filter(!\.isWholeNumber) // "abc"

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

I guess you want to write something like digitsExceptForFive.contains.not.

Sadly, you can't, because that would require the type (Int) -> Bool to have a not method. Functions cannot have methods.

You could write an overload of operator ! to do it (letting your first attempt work), but I wouldn't recommend it. Operator overloads tend to slow down compile times and confuse later readers of the code.

I recommend you just write it this way:

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

This language support needs to be added to Swift for ergonomic semantics. Please refer to the evolution pitch discussion on the Swift Evolution forums.