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.
- You want
configure
to do nothing whenT
is neitherSubTypeAOfBaseClass
norSubTypeBOfBaseClass
- you only want
MyClass<SubTypeAOfBaseClass>
andMyClass<SubTypeBOfBaseClass>
to be valid types -MyClass<BaseClass>
andMyClass<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 toCodable
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
Iterating Over an Nsorderedset
Why Do I Get "Static Member '...' Cannot Be Used on Instance of Type '...'" Error
Removing a Closure from an Array
How to Integrate Uisearchcontroller with Swiftui
Compactmap on Sequence() Not Lazy
Replacement for _Stdlib_Getdemangledtypename() in Swift 2.2
Anonymous User in Realm Mobile Platform
Swift:How to Handle a Lot of Textures in Memory
Scenekit Physics Add Velocity in Local Space
Xcode 8 Does Full Project Rebuild
Crop Image According to Rectangle in Swiftui
Changing Font Color of Uibarbuttonitem
Programmatically Create an Nsviewcontroller Without an Xib in Swift 3
Convert JSON Anyobject to Int64
Swift; Delegate Embedded View Controller and Parent
Swiftui: Update Navigationview After Deletion (Ipad)
How to Check the Type of a Variable Against Another Variable in Swift