Is there anyway to do OR generic composition?

extension Numeric where Self == Int | Double {   // apply if Self is Int or Double
    // blah
}

?

Not as such. You can, however, conform Int and Double to a custom protocol IntOrDouble and then write extension Numeric where Self: IntOrDouble.

7 Likes

What exactly is the intent here? Is there a reason you need Int, Double, and nothing else?

1 Like

No, I only use this to ask the question.

My actual use case:

protocol NSNumberConvertible { }
extension NSNumberConvertible {
    var asNSNumber: NSNumber {
        // just force cast b/c we only conform this to known NSNumber convertible Numeric types
        self as! NSNumber
    }
}
extension Decimal: NSNumberConvertible { }
extension Double:  NSNumberConvertible { }
extension Float:   NSNumberConvertible { }
extension Int8:    NSNumberConvertible { }
extension Int32:   NSNumberConvertible { }
extension Int:     NSNumberConvertible { }
extension Int64:   NSNumberConvertible { }
extension UInt8:   NSNumberConvertible { }
extension Int16:   NSNumberConvertible { }
extension UInt:    NSNumberConvertible { }
extension UInt64:  NSNumberConvertible { }
extension UInt16:  NSNumberConvertible { }
extension Bool:    NSNumberConvertible { }

// Prefab one. Is there better way? Can't put this in SpellOutNumberFormatStyle with static computed property
// because with static computed property it's re-computed everytime
let spellOutNumberFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.numberStyle = .spellOut
    return formatter
}()

struct SpellOutNumberFormatStyle<Value: NSNumberConvertible>: FormatStyle {
    func format(_ value: Value) -> String {
        spellOutNumberFormatter.string(from: value.asNSNumber)!
    }
}

extension FormatStyle where Self == SpellOutNumberFormatStyle<Decimal> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<Double> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<Float> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<Int8> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<Int32> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<Int> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<Int64> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<UInt8> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<Int16> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<UInt> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<UInt64> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<UInt16> {
    static var spellOut: Self { .init() }
}
extension FormatStyle where Self == SpellOutNumberFormatStyle<Bool> {
    static var spellOut: Self { .init() }
}

If there is OR generic type composition, then can skip all those boiler plate. Swift is all about skipping unnecessary ceremony.

Ah, that explains a lot. Foundation.NSNumber, by virtue of predating Swift, is a lot clunkier and dangerous than more modern types. It’s basically a textbook example of why you should avoid explicit overloading in favor of generic programming.

For that and other reasons (it’s practically an existential), it is best to avoid NSNumber as much as possible. I recommend forgoing any convenience initializers or methods that bury its use.

By the way, type casting is not how you convert between types in Swift. It’s only for upcasting and downcasting, which you are unlikely to be doing often anyway: use an initializer in all other cases.

On the bright side, type casting isn’t dangerous: as! is guaranteed to either succeed or terminate with a fatal error, just like try! and optionalNilValue!.

1 Like

NSNumber seems to be magic: you can cast to and from most numeric types. Don't know how this work.

The way I'm doing it, there is no "danger": it won't compile if you use the "wrong" type.

That’s one of the hard-coded exceptions, I suppose.

I think most people accept bridging like that as a necessary evil to spur early adoption of Swift.