Custom operators, global vs static; pattern matching

Two some distinct issues, but also sort of related to each other...

Consider the following:

let permittedSpeeds = 1 ... 200                  
let speeds = [0, 200, 100, 300]                  
for speed in speeds {                            
    print("Speed \(speed): ", terminator: "")    
                                                 
    if permittedSpeeds ~= speed {                
        print("permitted")                       
    }                                            
    else {                                       
        print("not permitted")                   
    }                                            
}                                                

Using the ~= operator is a more readable (as someone recently said in another thread) replacement for "contains":

if permittedSpeeds.contains(speed)

No question about that. Since Set has a contains method I tried the following:

let vowels = Set<Character>(["a", "e", "i", "o", "u"])                  
let letterY = Set<Character>(["y"])                                     
let letters = Array<Character>(["a", "b", "c", "d", "e", "x", "y", "z"])
                                                                        
for letter in letters {                                                 
    print("Letter \(letter) is a ", terminator: "")                     
                                                                        
    if vowels ~= letter {                                               
        print("vowel")                                                  
    }                                                                   
    else {                                                              
        print("consonant")                                              
    }                                                                   
}                                                                       

This does not compile. Looks like set does not have an ~= operator that invokes the contains method. But its easy enough to create:

extension Set {                                                              
    public static func ~= (pattern: Set<Element>, value: Element) -> Bool {  
        return pattern.contains(value)                                       
    }                                                                        
}                                                                            

Question 1: Is there any particular reason why this ~= operator is not in the Swift standard library?

Before doing the above I did it the following way:

public func ~= <Element: Any> (pattern: Set<Element>, value: Element) -> Bool {
    return pattern.contains(value)                                             
}                                                                              

Both methods (!!) work. In fact, both can be present in the same source file, but the Set extension is always (??) invoked in that case.

Is there any reason to prefer one over the other?
Are they in fact "the same", or do they only look the same for the few cases I've tested?

Finally, to my mind the following is actually more readable:

    if letter ~= vowels { ... }

I think of it as being similar to the following SQL:

where letter in ('a','e','i','o','u')

So I added this as well (inside the Set extension):

public static func ~= (value: Element, pattern: Set<Element>) -> Bool {
    return pattern.contains(value)                                     
}                                                                      

Now I can put the value that I want to check if its in the set on the left and the set itself on the right.

I believe the version with pattern, value (rather than value, pattern) is necessary in order to use a switch statement:

for letter in letters {                            
    print("Letter \(letter) is a ", terminator: "")
                                                   
    switch letter {                                
    case vowels:                                   
        print("vowel")                             
    case letterY:                                  
        print("Y")                                 
    default:                                       
        print("consonant")                         
    }                                              
}                                                  

This rather surprised me. But in any case, it seems to me that it would be useful to have both in the standard library (both for Set and also RangeExpression).

extension RangeExpression {                                       
    public static func ~= (value: Bound, pattern: Self) -> Bool { 
        return pattern.contains(value)                            
    }                                                             
}                                                                 

Am I missing something? Were these just missed or deemed not important?

This might be a good addition, particularly for use in switch statements, but I haven't thought through all the details. You might want to consider if this should apply more broadly, perhaps to all Sequences or Collections. Set seems straightforward because of O(1) performance, but perhaps O(n) would be fine also.

extension Sequence where Element: Comparable {
  public static func ~=(pattern: Self, value: Element) -> Bool {
    return pattern.contains(value)
  }
}
1 Like

Sounds good to me! :slight_smile:

My understanding is that ~= was invented for switch statements, and only really used on its own as an afterthought. You probably don't want to add a reversed overload for any of these because that would affect switch statements very weirdly!

let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
switch vowels {
case "e": print("matches!")
default: break
}

For the other question, do we want to say that your example is valid, where case vowels means "if the switched-upon value is any of the elements in vowels"? I'm not sure, but you could go through the Swift Evolution Process if you feel like it should be yes.

2 Likes

Mind you, there is a difference between switching on vowels and switching on "e".