Why 'There Cannot Be More Than One Conformance, Even with Different Conditional Bounds'

Why 'there cannot be more than one conformance, even with different conditional bounds'?

Is it impossible? Yes and no. It's not currently possible in Swift, as it has been implemented. It is in principle possible to be implemented.

The name for this is "overlapping conformances", and it was explicitly and purposely rejected. You can find the rationale in the "Alternatives considered" section of SE-0143 Conditional conformances. The TL;DR is: because it's really complicated.

Without knowing more about what exactly you were trying to use this for, there's not much direction we can provide.

How can I get conditional protocol conformance with a protocol's parent?

The question as posed, "How can I get conditional protocol conformance with a protocol's parent?" is meaningless, because a protocol always conforms with its parent; there is no "conditional" about it.

As for your actual code, the problem is the phrase A<Resource>. Saying A<Resource> is not a correct resolution of A's T. You need to resolve T as a class, struct, or enum — not as a protocol.

For example, if you have a class B that conforms to Resource, you can declare

let a = A<B>()

and all is well.

if there was a way to let A<Resource> know that it will always be working with concrete types that conform to Codable

Well, as I said, your code compiles fine as soon as A is working with a concrete type that does conform to Codable. So if that's what A will always be working with, there's nothing more to do. You could of course tell A that its T will always conform to Resource (which by definition will always conform to Codable):

class A<T:Resource> {}

Swift Generic Classes and Extensions with Conditional Generics

Consider the case when T is BaseClass, or when T is AnotherSubclass that I defined as

class AnotherSubclass : BaseClass {

}

What would happen? You haven't declared a conformance to Configure when T is AnotherSubclass!

There's really only two (not bad) choices here.

  1. You want configure to do nothing when T is neither SubTypeAOfBaseClass nor SubTypeBOfBaseClass
  2. you only want MyClass<SubTypeAOfBaseClass> and MyClass<SubTypeBOfBaseClass> to be valid types - MyClass<BaseClass> and MyClass<AnotherSubclass> would give compiler errors.

Choice 2 is not possible in Swift. That would require something similar to the sealed types in Java or Kotlin.

Choice 1 can be done like this:

class BaseClass {
...
func configure() {

}
}

class SubTypeAOfBaseClass: BaseClass {
...
override func configure() {
print("Configuring SubTypeAOfBaseClass")
doSomethingA()
}
}

class SubTypeBOfBaseClass: BaseClass {
...
override func configure() {
print("Configuring SubTypeAOfBaseClass")
doSomethingB()
}
}
class MyClass<T: BaseClass> {

let aThing = T()

func someMethod() {
aThing.configure()
}
}

You might notice that the each implementation of configure has been moved to the base classes. If you want to implement them all in MyClass, you must check the type by hand:

class MyClass<T: BaseClass> {

let aThing = T()

func someMethod() {
if let selfA = self as? MyClass<SubTypeAOfBaseClass> {
selfA.configure()
} else if let selfB = self as? MyClass<SubTypeBOfBaseClass> {
selfB.configure()
}
}
}

extension MyClass where T == SubTypeAOfBaseClass {
func configure() {
print("Configuring SubTypeAOfBaseClass")
aThing.doSomethingA()
}
}
extension MyClass where T == SubTypeBOfBaseClass {
func configure() {
print("Configuring SubTypeBOfBaseClass")
aThing.doSomethingB()
}
}

This is because of the second problem in your code - different parameterisations of a generic type, MyClass<SubTypeAOfBaseClass> and MyClass<SubTypeBOfBaseClass>, can't conform to a protocol differently. This is a limitation of Swift, unfortunately. See here for more info.

Unable to cast Array Codable to Codable

In accordance with the laws of conditional conformance that went into effect in Swift 4.2:

  • An array of some type (class, struct, or enum) that conforms to Decodable is decodable.

  • An array of the protocol Decodable is not, because a protocol does not conform to itself.

(What was happening before Swift 4.2 was that conditional conformance didn't exist and we were just getting a kind of universal pass; you could treat any array as decodable and you wouldn't hit a problem until runtime if you were wrong. Now, with conditional conformance, the compiler actually looks at the element type.)

Conditional Protocol Conformance: Cannot convert value of type 'Array _ ' to specified type '[UInt8]'

In order for Array<UInt8> to be BinaryCodable, its element type UInt8 must be BinaryCodable, which it isn't. That protocol has
default implementations of its required methods, but conformance must
still be declared explicitly:

extension UInt8: BinaryCodable {}

Then your extension String compiles,
and you can even get rid of the forced cast as! BinaryCodable in the
encoding method (and using guard allows to save one line):

extension String: BinaryCodable {
public func binaryEncode(to encoder: BinaryEncoder) throws {
try Array(self.utf8).binaryEncode(to: encoder)
}

public init(fromBinary decoder: BinaryDecoder) throws {
let utf8: [UInt8] = try Array(fromBinary: decoder)
guard let str = String(bytes: utf8, encoding: .utf8) else {
throw BinaryDecoder.Error.invalidUTF8(utf8)
}
self = str
}
}

Extentions for more than one protocol at once

In general, it is not possible to write a single extension on multiple protocols. This would require the compiler to figure out the set intersection of all the members in all those protocols (those are the members you have access to in the extensions), which the compiler cannot do.

To avoid code duplication, you would need to work out the members that you need - in your case init(integerLiteral:) and <=, put those in your own protocol, and make the concrete types you want the extension to apply to, conform to your own protocol:

// inheriting from Comparable and ExpressibleByIntegerLiteral gives you the members you need
protocol BinaryNumbers : Comparable & ExpressibleByIntegerLiteral {

}

extension BinaryNumbers {
func atMost127() -> Bool {
self <= 127
}
}

extension Int: BinaryNumbers {}
extension Int8: BinaryNumbers {}
extension Int16: BinaryNumbers {}
extension Int32: BinaryNumbers {}
extension Int64: BinaryNumbers {}
// ... plus the unsigned versions, if you need them

extension Float16: BinaryNumbers {}
extension Float32: BinaryNumbers {}
extension Float64: BinaryNumbers {}
extension Float80: BinaryNumbers {}

Now you might ask, why don't we just make an extension of Comparable & ExpressibleByIntegerLiteral then, as they provide all the members we are using? Well, because that's not a nominal type, and you can't write extensions on non-nominal types :(

However, you could write it in this way:

// now you don't need your own "BinaryNumbers"!
extension Comparable where Self: ExpressibleByIntegerLiteral {
func atMost127() -> Bool {
self <= 127
}
}

You are only able to do this because the two members you need all came from protocols. If for some reason you need a member that both BinaryFloatingPoint and BinaryInteger has, but isn't provided by any of the protocols they conform to, then you need to write your own protocol with those members, and manually conform everything to that protocol.



Related Topics



Leave a reply



Submit