Custom infix operators and optionals

Viewed 131
class TreeNode: Equatable {
    static func ==(lhs: TreeNode, rhs: TreeNode) -> Bool {
        lhs.val == rhs.val && lhs.left == rhs.right && lhs.right == rhs.left
    }
    
    var val: Int = 0
    var left, right: TreeNode?
}

This code compiles and even works. But why? left and right variables are optional, isn't I supposed to unwrap it first in the body of static func ==?

Actually it isn't quite an equation. As you can see it's rather some sort of symmetrical equation. So I would like to define custom operator with different name for this purpose:

infix operator =|=: ComparisonPrecedence
class TreeNode {
    static func =|=(lhs: TreeNode, rhs: TreeNode) -> Bool {
        lhs.val == rhs.val && lhs.left =|= rhs.right && lhs.right =|= rhs.left
    }
    
    var val: Int = 0
    var left, right: TreeNode?
}

And now it doesn't compile due to the reason I've mentioned earlier. It wants me to unwrap the optionals first.
Actually it would be great if it "just works" like in the case of "=="))) Because not having to unwrap the optionals explicitly would be convenient here.

So I want to understand why it behaves differently in these two situations.

1 Answers

This code compiles and even works. But why?

It is simply because there is an == operator declared for all Optional<Wrapped> where Wrapped is Equatable, like this:

static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool

TreeNode is Equatable in your first code snippet, so it works.

In your second code snippet, you haven't declared a =|= operator that operates on two TreeNode?. You can do that by either putting this in global scope...

func =|= (lhs: TreeNode?, rhs: TreeNode?) -> Bool {
    switch (lhs, rhs) {
    case (nil, nil): // both nil
        return true
    case (let x?, let y?): // both non-nil
        return x =|= y // compare two non-optional tree nodes
    default:
        return false
    }
}

or writing an Optional extension:

extension Optional where Wrapped == TreeNode {
    static func =|= (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
        switch (lhs, rhs) {
        case (nil, nil): // both nil
            return true
        case (let x?, let y?): // both non-nil
            return x =|= y // compare two non-optional tree nodes
        default:
            return false
        }
    }
}

But as Leo Dabus said, I'd just conform to Equatable and not create your own operator. Conforming to existing protocols allows you to use TreeNode with many APIs in the standard library, such as Array.contains(_:).

Related