Swift Conditional Conformances with Generic Type

Swift Conditional conformances with Generic Type

A constraint on the extension can restrict the placeholder type E to concrete types or protocols, for example:

extension Box where E: Item<Any> {}
extension Box where E == String {}
extension Box where E: Numeric {}

But you cannot put a generic constraint on the extension:

extension Box<A> where E: Item<A> {}
// Error: Constrained extension must be declared on the unspecialized generic
// type 'Box' with constraints specified by a 'where' clause

The solution is to restrict the method instead:

extension Box  {
func mapOnItem<A, B>(function: (A) -> B) -> Box<Item<B>> where E: Item<A> {
return Box<Item<B>>(val: Item(val: function(self.value.value)))
}
}

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.

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.

Workaround for conditional conformance in Swift (was: Adding protocol to a constrained generic types)

An updated answer now that we have Swift 4.2, conditional conformance is now added as a feature.

From their spec on Github, this sort of code is now valid

   extension List : Printable where T: SomeType {
func className() -> String {
return "List<SomeType>"
}
}

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 Conditional Conformance with Any

You can create a generic method and remove the constraint from the extension:

extension Sequence {
func reduced<T>() -> Result<[T], Error> where Element == Result<T, Error> {
reduce(.success([])) {
switch $0 {
case .failure: return $0
case let .success(array):
switch $1 {
case let .failure(error): return .failure(error)
case let .success(value): return .success(array + CollectionOfOne(value))
}
}
}
}
}

Btw you can also use a plain loop and provide an early exit at the first failure:

extension Sequence {
func reduced<T>() -> Result<[T], Error> where Element == Result<T, Error> {
var array: [T] = []
for result in self {
switch result {
case let .failure(error): return .failure(error)
case let .success(value): array.append(value)
}
}
return .success(array)
}
}

A more concise approach would be to create a generic method that throws or return an array with all successes:

extension Sequence {
func successes<T, E>() throws -> [T] where Element == Result<T, E>, E: Error {
try map { try $0.get() }
}
}

Playground testing:

let z: [Result<Int, MyError>] = [.success(1),
.success(2),
.success(3)] //,
//.failure(.blah)]
do {
let successes = try z.successes()
print(successes) // [1, 2, 3]
} catch {
print(error)
}

Extending array for object with generic type gives error

What you need is to move the constrain from the extension to the method, create a generic Equatable type and add a where clause constraining the collection Element to Foo

extension Collection {
func updateElement<T: Equatable>(element: Element) where Element == Foo<T> {

}
}


Related Topics



Leave a reply



Submit