Casting a type to a Protocol with Self requirements

cast

(Adrian Zubarev) #9

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.


(Erik Little) #10

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.


(Adrian Zubarev) #11

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.


(Vincent Esche) #12

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.


(Adrian Zubarev) #13

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


(Vincent Esche) #14

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


(Enrique Lacal) #15

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


(Enrique Lacal) #16

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


(Vincent Esche) #17

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.


Questions about the future of Protocols with Associated Types
Find a post by what it links to?
Opaque result types
Questions about the future of Protocols with Associated Types
(Erik Little) #18

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


(Enrique Lacal) #20

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!


(Adrian Zubarev) #21

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.


(Enrique Lacal) #22

I want to access all the functionalities from Model in MyModel and in Model I want to cast a value Any to Model so that I can call a function implemented by Model. Maybe this is impossible...

if let modelValue = value as? Model {
 modelValue.getModel() 
}

Maybe I can have a generic constraint on the function? Which is Self? Is that possible?


(Adrian Zubarev) #23

This is confusing.

Here is a test snipped (I only wrote it down but never tested it, but it should work):

protocol Model {
  func getModel() -> Model
}

struct MyModel : Model {
  func getModel() -> Model {
    print("my model")
    return self
  }
}

let test: Any = MyModel()
if let model = test as? Model {
  model.getModel() // returns `Model` but also prints `"my model"` in this situation
}

It's not what you want?


(Enrique Lacal) #24

Sorry - It is quite confusing to explain...

I want to return the Self type so when the user calls getModel() is gets back MyModel and not the type Model


(Enrique Lacal) #25

So
MyModel().getModel() // Returns MyModel and not Model


(Adrian Zubarev) #26

I understand that you want that on functionality on the same type, but what is the cast for then? What are you trying to extract with the dynamic cast then?

myModel.getModel() will return again MyModel which is basically the same as just myModel.


(Enrique Lacal) #27

I want the dynamic cast for recursion - I have a MyModel that could have a Model so I want to call that getModel() on the nested Model:

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

extension Model {
  func getModel() -> Self {
    var values // Values of the Model as Dictionary -- [String: Any]
    if let modelValue = values[0] as? Model {
       _ = modelValue.getModel() // Throwing out the result
    }
    return self
  }
}

struct NestedModel: Model {
  let age: Int
}

struct MyModel: Model {
  let name: String
  let nestedModel: Model
}

(Adrian Zubarev) #28

Sorry I'm still confused by that. You cannot have a bare Model property due to Self requirement. I'd love to help but I'm having hard times understanding what you're building. You can help me by writing doing some 'wish' API and some result types with some comments on why you need those to be like you wish. However I'll reply a little bit later.


(Enrique Lacal) #29

Maybe this example helps:

public protocol Model: Codable {
  func save(completion: (Self?, Error?) -> Void)
}

extension Model {
  func save(completion: (Self?, Error? -> Void) {
    var values = DictionaryEncoder(encode: self)
    for (key, value) in values {
      if let modelValue = value as? Model {
        modelValue.save { model, error in
           if let error = error {
              completion(nil, error)
              return
           }
            addForeignKey(to: values)
        }
      }
   } 

   let query = InsertQuery(from: values)

   // Execute INSERT statement in the database
   query.execute(using: connection)  { error
      completion(self, error)
   }
  }
}

struct NestedModel: Model {
  let age: Int
}

struct MyModel: Model {
  let name: String
  let nestedModel: Model
}

let nestedModel = NestedModel(age: 24)
let myModel = MyModel(name: "Enrique", nestedModel: nestedModel)

myModel.save { model,  error in // model is of type MyModel 
   // Do something
}

When saving a Model, I also want to save the nested Models