Extend type that conforms to protocol where associated type is *not* some type

The short version:

Is it possible to limit an extension to a type where some associatedtype is not a specific type. For example:

protocol Foo {
    associatedtype SomeAssociatedType
}

extension Foo where Self.SomeAssociatedType != Void {
    // . . .
}

The longer version:

I have a number of types I'm defining that describes steps in a series of operations. Each operation has a single input type, and a single output, described by a pair of associated types.

protocol Operation {
    associatedtype Input
    associatedtype Output

    func perform(with input: Input) -> Output
}

I have some code that allows a chain of operations to be created where one Operation output matches another Operation input. For example with this silly example:

struct ConvertIntToDoubleOperation: Operation {
    typealias Input = Int
    typealias Output = Double

    func perform(with input: Int) -> Double {
        Double(input)
    }
}

extension Operation where Self.Output == Int {
    var toDouble: AnyOperation<Self.Input, Double> {
        self.then(ConvertIntToDoubleOperation())
    }
}

// let double = someIntegerOperation.toDouble

In some cases though, an operation may define the end of the chain of operations, by specifying that its output type is Void. Another silly example:

struct PrintOperation<T>: Operation {
    typealias Input = T
    typealias Output = Void

    func perform(with input: T) -> Void {
        print(input)
    }
}

In this case, nothing can be chained after this.

However, now I want to add a function for adding this operation to the chain. And I want to add a function that will be added to all transactions who have an associated type that is not Void().

In other words... I want to allow this:

someIntegerOperation.toDouble.print
// or...
someIntegerOperation.print

But not:

someIntegerOperation.toDouble.print.print
// or...
someIntegerOperation.print.print

As the output of print is a Void, so it doesn't make sense to chain another operation after it.

Ideally I could do something like this:

extension Operation where Self.Output != Void {
    var print: AnyOperation<Self.Input, Void> {
        self.then(PrintOperation())
    }
}

But I cannot work out what the syntax should be here. It's easy to define the extension where something is true, but how do we handle cases where something is false? Is this even possible?

I think there is some detailed explanation why this is not possible somewhere here... but I don't remember exactly :joy:
Even with this limitation, you might be able to achieve something similar:
Afaik, Swift always picks the best fit when looking for an implementation — so when you supply the Void case explicitly, that should have higher precedence over more generic options.
So at least, it should be possible to produce a runtime error — or, maybe you can even use some unavailable-annotation to catch those cases during compilation.

1 Like