Can protocol conform to other protocol with using where condition

Can I make my protocol to confirm to other protocol with condition?

protocol MyCustomString {
    var inString: String { get }
}

extension Int: MyCustomString {
    var inString: String { String(self) }
}

extension Double: MyCustomString {
    var inString: String { String(self) }
}

extension String: MyCustomString {
    var inString: String { self }
}

I know the following code won't work.

// extension of protocol 'Collection' cannot have an inheritance
extension Collection: MyCustomString where Element == MyCustomString {
    var inString: String {
        self.map{$0.inString}.joined(separator: ", ")
    }
}

Is that the only way? using extension on struct or class? the example below that using extension on Array.

extension Collection where Element == MyCustomString, Self: MyCustomString {
    var inString: String {
        self.map{$0.inString}.joined(separator: ", ")
    }
}
extension Array: MyCustomString where Element == MyCustomString {}

Any better way the protocol to conform a protocol according to some conditions?

1 Like

No, you cannot conform one protocol to another in an extension. You can only do that in the original declaration.

1 Like

This is unfortunately a limitation of protocols. Alternatively, you could use a value witness type, like so:

struct MyCustomString<A> {
    var inString: (A) -> String
}

extension MyCustomString where A == Int {
    static let int = Self { String($0) } // alternatively Self(String.init)
}

extension MyCustomString where A == Double {
    static let double = Self { String($0) }
}

extension MyCustomString where A == String {
    static let string = Self { $0 }
}

extension MyCustomString where A: Collection {
    static func collection<B>(of witness: MyCustomString<B>) -> Self where A.Element == B {
        return Self { collection in
            collection
                .map { witness.inString($0) }
                .joined(separator: ", ")
        }
    }
}

func printCustomString<A>(value: A, type witness: MyCustomString<A>) {
    print(witness.inString(value))
}

printCustomString(value: 2.0, type: .double)
// 2.0

printCustomString(value: [1, 2, 3, 4, 5], type: .collection(of: .int)) 
// 1, 2, 3, 4, 5

Something which I really appreciate about these value witness types is that you can implement some really interesting higher-order functions on them. For instance, a "pullback" function, where when you give it a function from (B) -> A, it gives you back a value witness type over B:

extension MyCustomString {
    func pullback<B>(_ f: @escaping (B) -> A) -> MyCustomString<B> {
        return MyCustomString<B> {
            self.inString(f($0))
        }
    }
}

struct Account {
    var transactionAmounts: [Double]
    var name: String
}

let anonymousAccountString: MyCustomString<Account> = MyCustomString
    .collection(of: .double)
    .pullback(\.transactionAmounts)

let someAccount = Account(transactionAmounts: [19.99, 21.45, 5.99, 1.99, 50.00], name: "Johnny Appleseed")
print(anonymousAccountString.inString(someAccount))
// 19.99, 21.45, 5.99, 1.99, 50.00

And we can even improve the ergonomics by using callAsFunction(_:):

extension MyCustomString {
    func callAsFunction(_ value: A) -> String {
        return self.inString(value)
    }
}

print(anonymousAccountString(someAccount))
// 19.99, 21.45, 5.99, 1.99, 50.00

If you're interested, @mbrandonw did a great talk about it: Protocol Witnesses - Brandon Williams - App Builders 2019.

2 Likes

Could someone raise a proposal to suggest it?
I am not good in writing a proposal.
but i think this feature should has.