Search code examples
swiftgenericsprotocolswrapperswift-protocols

How to pass through to a base value in a generic wrapper struct?


Given a generic wrapper struct in the following form:

struct Observable<Base> {
    var base: Base
}

How can I pass through functionality that is applied to a value of type Observable to the inner Base? This can rather easily be done, for instance, with the Equatable protocol by writing an extension, declaring the requirements func == and just calling through to an existing conformance of Base to Equatable, like this:

extension Observable: Equatable where Base: Equatable {
    static func == (lhs: Observable<Base>, rhs: Observable<Base>) -> Bool {
        return lhs.base == rhs.base
    }
}

How could I do the same for pattern matching, e.g. so that I can switch, for example, on a value of type Observable<String?> directly, just like I would do if it wasn't wrapped in Observable? An example I'd like to solve and see working would be:

enum Availability {
    case available
    case notAvailable
}

let observableDescription = Observable<Availability>(.notAvailable)

switch observableDescription {
    case .available: // …
    case .notAvailable: // …
}

Bonus question: Is it possible to provide conformance to ExpressibleByArrayLiteral to the generic type Observable so that I can write let collection: Observable<[Int]> = []?


Solution

  • Two structs for two questions

    Expression Pattern

    Equatable actually have ~=(_:_:) as a requirement:

    extension Observable: Equatable where Base: Equatable {
        static func == (lhs: Observable<Base>, rhs: Observable<Base>) -> Bool {
            return lhs.base == rhs.base
        }
    
        static func ~= (pattern: Base, value: Self) -> Bool {
            pattern ~= value.base
        }
    }
    

    and now you can just do :

    let observableDescription = Observable<Availability>(base: .notAvailable)
    switch observableDescription {
    case .available:
        print("available")
    case .notAvailable:
        print("notAvailable") // "notAvailable\n"
    default:
        print("default")
    }
    

    ExpressibleByArrayLiteral

    For this one you will need an base array:

    struct ObservableSequence<Base>: ExpressibleByArrayLiteral where Base: Sequence {
        var base: [Base.Element]
        init(arrayLiteral elements: Base.Element...) {
            base = Array(elements)
        }
    }
    

    usage:

    
    let emptyCollection: ObservableSequence<[Int]> = []
    print(emptyCollection.base) // "[]\n"
    let collection: ObservableSequence<[Double]> = [1.0, 3.0, 3.0, 7.0]
    print(collection.base) // "[1.0, 3.0, 3.0, 7.0]\n"