Casting a type to a Protocol with Self requirements

I have a protocol Model

public protocol Model: Codable {
  func getModel() -> Self
}

I then have a dictionary of type [String: Any?] which I am iterating through and trying to perform an action if the value is a Model:

for _, value in dictionary {
   if let modelValue = value as? Model {
     // Do Something
  }
}

I get the following error:

error: protocol 'Model' can only be used as a generic constraint because it has Self or associ
ated type requirements

I have a workaround which is to create a dummy protocol:

public protocol AModel {}

And make Model conform to AModel:

public protocol Model: Codable, AModel {}

Then I can do:

if let modelValue = value as? AModel 

Is there a better workaround?

Many Thanks

I updated the title since it was a little bit misleading (Generic Protocols are not supported, yet).

What is it that you're trying to extract or get access to by casting to a specific protocol?

I am trying to call the methods from that protocol

You can only do it like that if you remove the Self (or in other cases associatedtype) requirement from the protocol. Then the dynamic cast will compile. The issue is that Self could be anything and if you know the dynamic type already then you don't need to cast it to Model again but rather cast it to the type you want.

Next up is the [String: Any?] type you have. Why is it Any? ? You already can store any optional type in Any directly.

let number: Int? = nil
let any: Any = number

There are two solutions that I see in your example:

  1. The outer function may be generic and be constrained to Model, this way you can extract a specific model and try to cast the values to that type.
  2. Rethink the protocol requirements in a way you get the most you need from the protocols without Self or associatedtype:
public protocol Model: Codable {
  func getModel() -> Model // it returns `Model` now
}

Thanks for the suggestion - I have restructured my protocol to remove the Self requirements as you suggested! And it solve the problem, I thought it was a must to have it.

Glad it worked for you. It's not a must. It really depends on what you want to build. Self requirement will force you to use the same type that you extend on conformance which is really handy in certain situations. If you return only Model then you only have the semantics of that particular protocol and don't know at compile time what other functions you could call on the conforming type.

1 Like

Ah and by the way, if the dictionary type is [String: Any?] you will shoot yourself into the foot if you'll use the subscript:

let value = dictionary["key"] // returns `Any??` or `Optional<Optional<Any>>`

The for loop will then iterate over Any? and your conditional cast will always fail because it's probably not Optional that conforms to Model, but the enclosing type.


That said, it really should be [String: Any] I think.

This is a bit misleading btw. We do have generic protocols: protocols with associatedtypes. Sure you don't have the same syntax as the rest of generics, but they accomplish the same thing. What we're missing, and is probably my #1 want now that we have conditional conformances, are generalized existentials. Because sometimes you really don't care what the concrete type of associated types are, you just care that they exist and that it has certain properties.

1 Like

Well here is it where you're wrong. They're not doing the same thing. It just happend that I had this gist opened. I think @regexident can confirm that GP's are not the same as protocols with associated types. We must use protocols with associated types in a generic context but they're not generic protocols because of that fact. Sure you can accomplish one thing in two ways then, but if you start building further you'll get to a point where one concept does not fit any longer and you'd need to switch to the other. It's of course a fact that both GP's and PAT's have a lot in common but they're not the same.

Hm, I see there is a subtle difference after re-reading the GenericsManifesto, thanks for pointing that out. I'm still holding out for generalized existentials though, since generic protocols are listed as "unlikely" for inclusion in Swift.

1 Like

TBH I still think the same way @regexident does that generalized existentials do not exclude generic protocols since they would only enable more generic algorithms that were not possible before or at least were super complicated.

Just to avoid confusion: The gist that @DevAndArtist linked to is not the actual manifest. The actual manifest is somewhat lacking when it comes to discussion/explaining the use of GPs and in particular how they differ from PATs or Existentials. The linked gist is an attempt of mine to fill that gap. I plan to make a PR for extending the actual manifest with a refined version of said gist.

1 Like

I'm really looking forward to this. Will this go through a public discussion before you make a PR to the generics manifesto?

Think so, yeah. I'll make sure to ping you when ready. :slight_smile:

2 Likes

Good catch - I was using the subscript :sweat:! I'll change to [String: Any]

I would also want to know - if possible :grinning:

Existentials, Protocols with Associated Types (PATs), Generic Protocols (GPs), Higher Kinded Types (HKTs) are disjoint language features aiming to solve different problem spaces.

Existentials:

Allow for implementing things like Collection<Element = Int>.
They allow for this by making the generic <T> concrete via <Element = Int> and applying some kind of type erasure. As such their instances are heap-allocated and make use of dynamic dispatch. You can only receive the concrete type from an existential by doing if let foo = existential as? Foo { … }.

If you "want to put instances of different generic types into a collection (or a type-erased variable, that preserves the concretized generic argument)", then you're probably looking for Existentials.

Example:

protocol List {
    associatedtype Element
}

struct Array<Element> { … }
struct LinkedList<Element> { … }

// existential:
typealias ListOfStrings = List<Element = String>

let listsOfStrings: [ListOfStrings] = [
    Array(["foo", "bar"]),
    LinkedList(["foo", "bar"])
]

I'm using List<Element = String> here as that how one defines existentials in Rust.
An alternative syntax would be List where Element == String.

A few people tend to use/propose List<Element> as syntax for existentials, but this would …

  • collide with the common syntax of Generic Protocols (GPs), should those ever get added.
    (They are my most missed feature of Swift, by far. Existentials being distant #2.)
  • give a wrong understanding of what the parameter is actually doing: It's not injecting a argument type into the type, its restricting the accepted argument types to a single type (i.e. where Element == String), effectively concretizing the generic argument.

Protocols with Associated Types (PATs):

PATs allow for abstracting over types with external and non-universal (i.e. specialized) type dependencies.
I.e. you might want to depend on a different external type for each conforming type.

Note: PATs are particularly useful when combined with generic types, but are not limited to those. (This frequent collocation of generic types and PATs probably is part of the reason many people confuse PATs for GPs.)

If you "want to have your protocol abstract over the one-to-one relationship between types that might differ depending on the implementing type", then you're probably looking for PATs.

Example:

protocol Sequence {
    associatedtype Element

    // ...
}

Generic Protocols (GPs):

GPs allow for implementing protocols (with or without associated types) multiple times on a single type.
A GP can have associated types of its own (and often has), but does not necessarily have to have those.

Use-case:
Right now overloaded functions cannot be abstracted over in protocols.
Let's say you have a type Vector<T>. It implements these functions:

// scaling with scalar:
static func * (lhs: Vector, rhs: Scalar) -> Vector
// dot-product with other vector:
static func * (lhs: Vector, rhs: Vector) -> Scalar

Now as an exercise to the reader, try to write a function func multiply<T> that is generic over T and takes the following arguments: (Vector, T). Vector has to be multipliable with T and the result of the function should be Vector if T == Scalar and Scalar if T == Vector.

For a more in-depth description of the problem and other use-cases of GPs see the aforementioned gist.

If you "want to have your protocol abstract over the one-to-many relationships between types that might differ depending on the implementing type", then you're probably looking for GPs.

Example:

protocol Multiplication<Rhs> {
    associatedtype Output
    
    func *(lhs: Self, rhs: Rhs) -> Output
}

// vector scaling:
extension Vector: Multiplication<Scalar<T>> {
    typealias Output = Vector<T>
    func *(lhs: Self, rhs: Rhs) -> Output { /* ... */ }
}

// dot product:
extension Vector: Multiplication<Vector<T>> {
    typealias Output = Scalar<T>
    func *(lhs: Self, rhs: Rhs) -> Output { /* ... */ }
}

// vector matrix product:
extension Vector: Multiplication<Matrix<T>> {
    typealias Output = Vector<T>
    func *(lhs: Self, rhs: Rhs) -> Output { /* ... */ }
}

Higher Kinded Types (HKTs)

Currently in Swift func map/func flatMap/func compactMap are defined on Sequence like this:

protocol Sequence {
    func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]
    func flatMap<T>(_ transform: (Self.Element) throws -> T?) rethrows -> [T]
    func compactMap<T>(_ transform: (Self.Element) throws -> T?) rethrows -> [T]
}

Wouldn't it be much more convenient if instead of all of them returning [T] for every implementation of Sequence, regardless of the implementing type, the functions would return Set<T> for Set<_> and Dictionary<K, V> for Dictionary<_, _>?

You would be able to go from …

let strings: Set<String> = Set(["foo", "bar", "baz"])
let lengths: Set<Int> = Set(strings.map { $0.count })

… to this …

let strings: Set<String> = Set(["foo", "bar", "baz"])
let lengths: Set<Int> = strings.map { $0.count }

Apart from being cleaner this would also spare us one costly heap allocation of a temporary Array<Int>.

If you "want something else that's not covered by either of the three above or a combination thereof", then you're probably looking for HKTs.

Example:

protocol Bindable {
    associatedtype Element
    associatedtype Output<T>: Bindable

    func map<T>(_ transform: (Self.Element) throws -> T) -> Output<T>
    func flatMap<T>(_ transform: (Self.Element) throws -> Output<T>) -> Output<T>
    func compactMap<T>(_ transform: (Self.Element) throws -> Output<T>) -> Output<T>
}

struct Array<Element>: Bindable {
    typealias Output<T> = Array<T>

    // ...
}

struct Set<Element>: Bindable<T> {
    typealias Output<T> = Set<T>

    // ...
}

Protocols with Generic Associated Types (GATs):

GATs (as being added to Rust right now) are a closely related, feasible alternative to HKTs.

20 Likes

I think we should move to discussion for this now :smiley:

1 Like

Hey @DevAndArtist, I have realised that actually I cannot remove the Self requirement in my Protocol because I have the function getModel() -> Self which need to return the type implementing the protocol. Example:

public protocol Model: Codable {
  func getModel() -> Self
}

extension Model {
  func getModel() -> Self {
    return self
  }
}

struct MyModel: Model {
  let name: String
}

MyModel.getModel() // Returns the type MyModel

If the Self requirement is removed - I would get the type Model

Maybe I'm missing something? Many Thanks!

The question is do you really need to access everything that MyModel has? If you do then you'll need Self, otherwise you can use Model instead.

But again if you use Self requirement then you cannot cast to Model since it does not make sence.

// what will `Self` be here? `MyModel`, `OtherModel?` it's impossible for the compiler to know
(value as Any) as? Model

If you need MyModel then you should dynamically cast to MyModel directly instead of Model. By the end of the day it really depends on what functionality you want to access from your models.