2
votes

Swift - Pattern Matching Enums with Associated Value with Commutativity

Swift 5.0, Xcode 10.2.1

I have an Expression enum in Swift.

enum Expression {
    indirect case add(Expression, Expression)
    indirect case subtract(Expression, Expression)
    indirect case multiply(Expression, Expression)
    indirect case divide(Expression, Expression)
    indirect case power(Expression, Expression)
    indirect case log(Expression, Expression)
    indirect case root(Expression, Expression)
    case x
    case n(Int)
}

extension Expression: Equatable {
    static func == (lhs: Expression, rhs: Expression) -> Bool {
        switch (lhs, rhs) {
        case let (.add(a, b), .add(c, d)) where a == c && b == d,
             let (.subtract(a, b), .subtract(c, d)) where a == c && b == d,
             let (.multiply(a, b), .multiply(c, d)) where a == c && b == d,
             let (.divide(a, b), .divide(c, d)) where a == c && b == d,
             let (.power(a, b), .power(c, d)) where a == c && b == d,
             let (.log(a, b), .log(c, d)) where a == c && b == d,
             let (.root(a, b), .root(c, d)) where a == c && b == d:
            return true
        case let (.n(a), .n(b)) where a == b:
            return true
        case (.x, .x):
            return true
        default:
            return false
        }
    }
}

First Attempt

I perform a lot of pattern matching on my Expression type. The commutativity of addition and multiplication made for much long pattern matching expressions. I wanted to find a way to simplify and shorten this, so I decided to make an ExpressionPattern enum and define an overload of the pattern matching operator (~=).

enum ExpressionPattern {
    case commutativeMultiply(Expression, Expression)
    case commutativeAdd(Expression, Expression)
}

func ~= (lhs: ExpressionPattern, rhs: Expression) -> Bool {
    switch lhs {
    case let .commutativeMultiply(a, b):
        switch rhs {
        case .multiply(a, b), .multiply(b, a):
            return true
        default:
            return false
        }

    case let .commutativeAdd(a, b):
        switch rhs {
        case .add(a, b), .add(b, a):
            return true
        default:
            return false
        }
    default:
        return false
    }
}

I want to be able to replace pattern matching statements like:

case let .add(.n(3), a), let .add(a, .n(3)) where a > 10: //matches (a + 3), (3 + a)
//...

With:

case let .commutativeAdd(.n(3), a) where a > 10: //matches (a + 3), (3 + a)
//...

When I tried to do this for the first time though, I got an error say "Pattern variable binding cannot appear in an expression".

Note: I am that this match works if I use the exact, final values without value binding, but I utilize this feature in many places throughout my project.

Attempt Usage:

let expression: Expression = Expression.add(.divide(.n(1), .n(2)), .subtract(.n(3), .n(4)))
switch expression {

case let .commutativeAdd(.subtract(a, b), .divide(c, d)): //Error: Pattern variable binding cannot appear in an expression.
    print("This matches: ((\(a) - \(b)) + (\(c) ÷ \(d))) and ((\(c) ÷ \(d) + (\(a) - \(b)))")

default: 
   break

}


Second Attempt

For my second attempt I changed the definition of Expression's == function, and tried to override its default implementation of ~=.

extension Expression: Equatable {
    static func == (lhs: Expression, rhs: Expression) -> Bool {
        switch (lhs, rhs) {
        case let (.add(a, b), .add(c, d)) where  (a == c && b == d) || (a == d && b == c),
             let (.subtract(a, b), .subtract(c, d)) where a == c && b == d,
             let (.multiply(a, b), .multiply(c, d)) where  (a == c && b == d) || (a == d && b == c),
             let (.divide(a, b), .divide(c, d)) where a == c && b == d,
             let (.power(a, b), .power(c, d)) where a == c && b == d,
             let (.log(a, b), .log(c, d)) where a == c && b == d,
             let (.root(a, b), .root(c, d)) where a == c && b == d:
            return true
        case let (.n(a), .n(b)) where a == b:
            return true
        case (.x, .x):
            return true
        default:
            return false
        }
    }

    static func ~= (lhs: Expression, rhs: Expression) -> Bool {
        return lhs == rhs
    }
}

Attempt Usage:

let expression: Expression = Expression.add(.divide(.n(1), .n(2)), .subtract(.n(3), .n(4)))
print(expression == .add(.subtract(.n(3), .n(4)), .divide(.n(1), .n(2)))) //Prints "true"

switch expression {
case .add(.subtract(.n(3), .n(4)), .divide(.n(1), .n(2))):
    print("Matched")
default:
    print("Not matched")
}

//Prints "Not matched"

Note: This should have ideally printed "Matched".


Question

How do I find a way to use full-fledged pattern matching while taking into account commutativity of certain cases.

Note: This must be able to be used for matching nested cases with commutativity (i.e. .add(.multiply(..., ...), ...)). This is especially important as a lot more cases are needed when matching something with both addition and multiplication.

1

1 Answers

-1
votes

I changed your second usage as below

switch expression {
case let e where e == .add(.subtract(.n(3), .n(4)), .divide(.n(1), .n(2))):
    print("Matched")
default:
    print("Not matched")
}

I think it will work.