Archive for October, 2017

Matching Regular Expressions

Last week, I wrote about Olivier Halligon’s elegant Set matching solution. Several people asked if this could be extended to pattern matching for other types, which it can. Today, I put together a little proof of concept for performing regular expression matching with strings.

I started off with the same struct plus stand-alone global pseudo-constructor approach I had used with sets. Doing so, let me have a RegexPatternMatcher type and implement a matching function to hide a constructor. After a bit of playing around,  I dropped the global function to use a normal initializer: RegexPatternMatcher("pattern") vs matching("pattern"). The call sites were longer but less garbage floating around.

Renaming the type to Regex provided a more succinct approach : Regex("pattern"). I don’t normally like using overly short type names to balance call sites but I felt that Regex said about all that needed to be spoken here:

I’m not a huge fan of NSRegularExpression. I can’t wait for a native Swift solution. The class is expensive and it relies on what feels like archaic string models that use NSRange. Olivier suggested I balance the expense with NSCache so I created a variation where converted my struct to a class, and used NSString patterns as the cache keys.

This allowed me to build each NSRegularExpression instance once and decouple the matching options from the pattern. I made the class match function ignore defaulted option arguments. Instead, I allow the cache to carry forward any matching policies that previously existed or overwrite those policies if you specify them explicitly.

There’s something wrong with my code because I could not get a cached regex to change its matching behavior even after I updated options. Look at lines 103 through 107 in this version. If you figure out what’s wrong, let me know. It returns true for all six tests, instead of four true then two false.

Anyway, it was an interesting exercise and a good way to start getting back to work as I recuperate.

A Beautifully Elegant way to Set-Match

Challenge: By default, Swift’s switch statement uses equality matching when testing Set instances (for example). So how can you switch that up to use containment instead of equality? For example, say you want to test a set of insets presented as Set<Inset>? Here’s the standard Swift solution for containment:

switch insets {
case let sides where sides.isSuperset(of: [.top, .bottom]) : ...
case let sides where sides.isSuperset(of: .top) : ...
case let sides where sides.isSuperset(of: .bottom) : ...
}

It’s not awful, but couldn’t there be a more beautiful way to allow pattern matching against a set using a more Swifty statement that didn’t abuse its where clause?

Solution: Olivier Halligon came up with the following approach. He built a custom Set containment struct whose pattern matching operator (~=) is customized to apply superset detection:

public struct SetContainmentMatcher<T: Hashable> {
    public let set: Set<T>
    
    public static func ~=(
        lhs: SetContainmentMatcher<T>, 
        rhs: Set<T>) -> Bool {
        return rhs.isSuperset(of: lhs.set)
    }
}

public func containing<T>(_ set: Set<T>) -> SetContainmentMatcher<T> {
    return SetContainmentMatcher(set: set)
}

To use, build your switch using the now-global containing function. The function enables you to test your set against other sets using standard switch pattern matching.

public enum Inset { case top, bottom, left, right }

let sides: Set<Inset> = [.top, .right]
switch sides {
case containing([.top, .bottom]):
    print("contains: top, bottom")
case containing([.top]):
    print("contains: top") // matches here
case containing([.bottom]):
    print("contains: bottom")
default:
    print("default case")
}

Nice, isn’t it? Make sure to test for the largest sets first. If you invert the logic here, testing [.top] before [.top, .bottom], you may end up with unexpected behavior.

More good reading here on Olivier’s blog.