Is there a way use generic constraints for a type or an array of that type?

Given this enum which represents the response from a network call:

public enum Response<T: Model> {
    case success(T)
    case failure(Error)
}

It can represent a response with a single Model. What I'm wondering is: is there is a way to constraint T so it can be either a single Model or an array Model?

public enum Response<T: Model || [Model]> {

If Model is a protocol, then one could utilize conditional conformances as follows:

extension Collection: Model
where Element : Model {
  // Model conformance here
}

This way [Model] would satisfy the T : Model requirement.

Interesting idea, that would likely work for me. But the compiler wasn't into it :sob:

Extension of protocol 'Collection' cannot have an inheritance clause

Right. It's only available in ToT compiler, and only under a special flag, IIRC.
The next best thing I can think of is wrapping the array of models into a trivial struct and make this struct conform to the protocol. Should be essentially the same for a small price, that is, you'd have to instantiate this new struct manually.

Sorry, you can't make protocols conditionally-conform to other protocols. They have to refine them in the original declaration :(

Not yet, anyways. Wait for SE-0143 to be implemented.

...?
SE-0134 is my proposal titled "Rename two UTF8-related properties on String."

1 Like

I really shouldn't be typing on mobile. Thanks for catching that!

Even then, you won't be able to. It was explicitly ruled out.

Extending protocols to conform to protocols

The most common request related to conditional conformances is to allow a (constrained) protocol extension to declare conformance to a protocol. For example:

extension Collection: Equatable where Iterator.Element: Equatable {
 static func ==(lhs: Self, rhs: Self) -> Bool {
   // ...
 }
}

This protocol extension would make any Collection of Equatable elements Equatable, which is a powerful feature that could be put to good use. Introducing conditional conformances for protocol extensions would exacerbate the problem of overlapping conformances, because it would be unreasonable to say that the existence of the above protocol extension means that no type that conforms to Collection could declare its own conformance to Equatable, conditional or otherwise.

You could add a conformance to the concrete Array type, though.

extension Array: Model where Element: Model {
  // ...
}
4 Likes

As others have suggested until Swift's conformances are more advanced, you'll need to wrap this in a struct.

struct ModelCollection<C: Collection>: Model where C.Element: Model {
    let value: C
}

Works OK for me. I tested by making an empty Model protocol and making Int conform to it; ModelCollection(value: [0]) type checked OK.

I'm assuming this doesn't fix your problem, but this is how I see it:

You want to accept either a Model instance (exactly one), or an array of Model instances (between 0...n).

So the case of accepting exactly one instance is actually a subset of accepting an array anyway.

Maybe there is a semantic difference in your case between receiving a Model instance and receiving a [Model] instance that contains exactly one element. But if not, I'd simplify to:

public enum Response<T: Model> {
    case success([T])
    case failure(Error)
}

I'll end up doing the everything is an array approach because I'm having too much trouble otherwise. I just wanted the caller to receive exactly what he wanted and I was wondering if it would be possible.

For future reference, this was my last attempt before deciding to go with the array everywhere approach:

public protocol Service {
    func get<T>(path: String, parameters: [String: Any], completion: @escaping (Response <T>) -> Void) where T: Model
    func get<T: Collection>(path: String, parameters: [String: Any], completion: @escaping (Response <T>) -> Void) where T.Element: Model
}

public enum Response<T> {
    case success(T)
    case failure(Error)
}

I was liking it at first but it started to require having "duplicated" methods everywhere.

One other option would be:

enum ModelEnum {
   case one(model: ModelProtocol)
   case many(models: [ModelProtocol])
}

extension ModelEnum : ModelProtocol { ... }

It is somewhat similar to the "wrap an array into a struct" approach, just more clearly states that only these two cases are possible.

1 Like