> This proposal replaces the current syntax with a simpler grammar that 
> prioritizes pattern matching but mirrors basic conditional binding. The new 
> syntax drops the case keyword and replaces = with ~=. The results look like 
> this:
> 
> guard let .success(value) ~= result { ... }
> guard .success(let value) ~= result { ... }
> if let .success(value) ~= result { ... }
> if .success(let value) ~= result { ... }
> guard let x? ~= anOptional { ... }
> if let x? ~= anOptional { ... }

I don't mind this syntax, but I'm not convinced this is worth doing if it's 
merely a syntax change. (It goes without saying that this is not phase 1.) But 
if it were motivated by a broader change, I think I could get behind it.

Suppose we introduced this concept:

        protocol Pattern {
                associatedtype Target
                associatedtype Bindings
                
                static func ~= (pattern: Self, target: Target) -> Bindings?
        }
        
        // Sometimes you just want the pattern on the other side.
        func =~ <PatternType: Pattern>(target: PatternType.Target, pattern: 
PatternType) -> PatternType.Bindings? {
                return pattern ~= target
        }

And further suppose that conditionals were altered to support `~=` and `=~` 
instead of `case =`. That is, a conditional with a pattern match in it would 
take the "then" branch if the `~=` returned `.some`, and would bind the 
variables in the returned tuple. In essence, this:

        if pattern ~= target { … }

Would be rewritten to:

        if let ([variables from pattern]) = (pattern ~= target) { … }

(One way to do this would be to support *any* Optional as a conditional, not 
just the results of a pattern match; this would essentially make `nil` into a 
false-ish value. But we tried that in the Swift 1 betas and didn't seem too 
happy with it.)

Enum cases would then have a dual nature; they could construct values, but they 
could also construct `Case` instances:

        let value = Foo?.some(Foo())
        // value: Foo?
        
        let pattern1 = Foo?.some(let value) as Case
        // pattern1: Case<Foo?, (value: Foo)>
        
        let pattern2 = Foo?.some(_) as Case
        // pattern2: Case<Foo?, (Foo)>
        
        let pattern3 = Foo?.some as Case
        // pattern3: Case<Foo?, (Foo)>
        
        let aFoo = Foo()
        let pattern4 = Foo?.some(aFoo) as Case
        // pattern4: Case<Foo?, (Foo)>

Note that all four patterns are some variant of `Case<Foo?, (Foo)>`; it's just 
a matter of re-labeling the second parameter. Hopefully you could do that with 
a cast:

        if (pattern as Case<Foo?, (foo: Foo)>) ~= optionalFoo {
                // use `foo` here
        }
        
        // Or with more powerful existentials:
        if (pattern as Pattern where Bindings == (foo: Foo)) ~= fooTarget {
                // use `foo` here
        }
        
        // Perhaps some sugar:
        if fooTarget =~ pattern as let value {
                // use `foo` here
        }

Elements with a label are bound to a variable by that name; elements with no 
label are not bound to any variable. 

`Case` would look something like this, with the actual implementation of `~=` 
being compiler magic:

        class Case<Enum, Associated>: Pattern {
                typealias Target = Enum
                typealias Bindings = Associated
                
                static func ~= (pattern: Case, target: Target) -> Bindings? { … 
}
        }

(I suppose it might instead be `Enum.Case<Associated>`—just depends on whether 
we want it to be magic or standard library-ish.)

So, what other patterns could we implement? Well, most notably, regular 
expressions:

        class Regex<Captures>: Pattern {
                typealias Target = String
                typealias Bindings = #concatenateTuples(($0: String), Captures)
                
                static func ~= (pattern: RegularExpression, target: String) -> 
Bindings? { … }
        }

 You would use them like this:

        if /ab(.(.)?)c/ ~= myString {
                // That's a Regex<($1: String, $2: String?)>.
                // A named capture syntax would allow you to rename $1 and up, 
or you could cast 
                // a regex you had received.
                // If string slicing is corrected so that substrings share 
indices with their parent string, 
                // you would not need a separate "get range of match" feature.
        }

But here's another neat use case:

        let gregorian = Calendar(identifier: .gregorian)
        if myDate =~ DateComponents(calendar: gregorian, month: 10, day: 31) {
                print("👻🎃🕷⚰🍫")
        }
        
        extension DateComponents {
                typealias Target = Date
                typealias Bindings = (DateComponents)   // Optionally bindable
                
                static func ~= (pattern: DateComponents, target: Target) -> 
Bindings? {
                        let calendar = self.calendar ?? Calendar.current
                        let timeZone = pattern.timeZone ?? TimeZone.current
                        
                        guard calendar.date(target, matchesComponents: pattern) 
else {
                                return nil
                        }
                        
                        return calendar.dateComponents(in: timeZone, from: date)
                }
        }

Anyway, just a related thought I had. Sorry for hijacking.

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to