Overloading for greater recognition

Switching over enums with associated types is the best. If bees had knees? Aforementioned knees would–most likely–involve switches and enums with associated types. A frustrating issue can arise, however, when you believe too completely in the power of this combination.

Motivation

I love the number 9 and want to shout to the heavens whenever it is input in whatever form. We will make an enum Number to represent numbers.

public enum Number : IntegerLiteralConvertible {
    case integer(Int)
    case floatingPoint(Double)

    public init(integerLiteral value: Int) {
        self = .integer(value)
    }
}

Excellent. Let us now switch over a user provided value to look for the number 9.

let value: Number = .floatingPoint(9)
switch value {
case .integer(9):
    print("hooraaaay!!!")
default:
    print("I feel nothing. Nothing at all.")
}

// outputs "I feel nothing. Nothing at all."

Problem

I do not receive the output that I expect when the user provides 9 as a floating point number. This is an unending source of woe as I want to give love to the 9's equally.

Solution

You might suggest that I just write an overload for equals and convert as appropriate and that is a workable solution for certain situations but what if this enum was the payload of another enum? == isn't called then. After much public bemoaning on the internet, I remembered our oft-forgotten helper ~=. Let us implement the pattern match operator ~= and change our switch just a little.

let value: Number = .floatingPoint(9.0)
public func ~=(pattern: Int, value: Number) -> Bool {
    switch value {
    case let .integer(v):
        return pattern == v
    case let .floatingPoint(v):
        return pattern == Int(v)
    }
}


switch value {
case 4:
    print("success!")
default:
    print("try again")
}

et voila! In this way, we get union-like behavior when we want it.

Caveat Emptor

1. Overrides

The problem with overriding ~= and/or == with this casting behavior, of course, is that Double and Int are two different representations of numbers which do not always convert in the ways that you would expect. I do not, at present, have a fix for that madness.

2. Double != Int

Be careful when trying to add any of this to a protocol. I tried to make Double conform to to this protocol

public protocol NumberLiteralMatchable : FloatLiteralConvertible, IntegerLiteralConvertible {
    func ~=(pattern: IntegerLiteralType, value: Self) -> Bool
    func ~=(pattern: FloatLiteralType, value: Self) -> Bool
}

and was met with strife and hardship.

3. No, Really, Overrides

I thought that I could avoid mentioning the interaction with (Float|Integer)LiteralConvertible that can occur for conforming structs. I was incorrect. I was terribly incorrect. If you use this strategy for a struct, understand that == will no longer be used when you switch and provide a literal for the case.