Improving the UI of generics

If that's not a generic protocol, what else is? Can you point me to some reading so I can follow your argument?

Just recently we got this direction approved into the generics manifesto, which adds some more parameterization to protocols and would help cover a lot (but not all) places where I otherwise would want a parameterized protocol (generic protocol). However it does require some proxy types and some boilerplate but at least it will solve a few issues in my code:

I wonder if Self<.Assoc == T> would be allowed as well at some point.

A "generic protocol" as Rust implements it, and many people argue for it, is a protocol with multiple independent conforming types, by contrast with associated types, which model a functional dependency between the Self type and the associated types (meaning that for any conforming Self type, you can infer there's one associated type). The most common examples of these I've seen are conversion-style protocols, where you want to be able to say that there's a relationship between arbitrary pairs of types, like (Int, Float), (Int, Double), etc. With only associated types, you wouldn't be able to express this without multiple conformances on Int.

In your example, it looks like really existentials with associated type constraints would be sufficient:

protocol P {
  associatedtype A, B
  init(a: A, b: B)
}
​
extension P {
  typealias Next<T> = any P<.A == T, .B == Self>
  func next<U>(value: U) -> Next<U> {
    .init(a: value, b: self)
  }
}
4 Likes

I have a couple of comments on this:

First, minor one. I'm reading these keywords as prefix operators, so I would expect last one to be meta any P, because it is a [metatype of]-[existential of]-protocol.

Second, major one. While I like the idea of existential abstracting generic types, the idea of associated existential seems fundamentally wrong to me.

Associated types associate types with types. Existential types wrap non-type entities as types by abstraction.

Given

protocol P { associatedtype A }
struct S: P { typealias A = Int }
struct Z<T: P> { ... }
  • Array<S> is a type
  • any Array is a type
  • Array is not, at least not a rank-1 type
  • any P is a type (where P is a protocol)
  • P is not
  • S.A is a type
  • P.A is not a type
  • T is a type
  • T.A is a type

Also for the same reason generic syntax Type<P> cannot be used to express P.Type. Currently all generic parameters are types, and P is not a type. You can have Type<any P>, but that's a P.Protocol.

You're right, this is a much better ordering. I had it backwards.

The idea here is that each type has an "existential" type implicitly associated with it, similar to how the type of the metatype is associated with it via .Type. For existentials and non-generic types this would just be an identity association, but for bound generics types (i.e. Array<Int>) and protocols metatypes this would reference the related existential type. The first post I made in this sub-thread on this topic (which used an earlier version of the syntax) contains some details that weren't stated in the post you quoted.

I hope this helps to clarify what is intended by the term "associated existential".

So association would be between Array<Int> and any Array? And between meta P and any P? I can imagine how the later would be useful, but first one really confuses me.

The association between Array<Int> and any Array can be used to make an encoding of higher-kindred types both more robust and more convenient. I’m not sure if there are any other use cases for it. That’s the one where I stumbled across it.

In any case, this is a relatively minor aspect of the language changes discussed in the related sub-thread. Changes along the lines discussed there would be nice to have for many much more practical reasons.

With the any modifier, there’s an opportunity to revise the current meta type syntax to be more obvious. P.Protocol, the type of P.self, would be (any P).Type, whereas the type of all T.self where T: P would be any P.Type if we say that P.Type is a generic constraint for a meta type conforming to P.

4 Likes

@Joe_Groff Do you see no chance to free up the .Type namespace in Swift?


Btw. @Nickolas_Pohilets here is the translation from Joe's notation to the other meta keyword I was using.

typealias Meta<T> = meta T

P.Protocol == (any P).Type == Meta<any P> == Meta<P> == meta P
    P.Type ==   any P.Type == any Meta<P> == any meta P

Why is there any in the last line?

Right now there are two kind of meta types which are merged into one type, which makes it really hard to work with and which also explains why there is no pure Swift implementation of type(of:) function.

Around Swift 3 end we were working on a proposal to try to push a meta type revamp though while you didn't need to provide an implementation as a proposal author.

Here is that document: https://github.com/DevAndArtist/swift-evolution/blob/refactor_existential_metatypes/proposals/0126-refactor-metatypes.md

Back then we sliced .Type and .Protocol into Type<T> and AnyType<T> where the last type is an existential like type but for meta types.

If we now rename Type<T> to Meta<T> exchange Any prefix by the any keyword, we'll get the exact same types as above:

  • AnyType<P> == any Meta<P> == any P.Type
  • Type<P> == Meta<P> == (any P).Type

Using Joe's syntax version type(of:) would probably look something like this:

func type<T>(of value: T) -> any T.Type

And a 'potential' subtype(of:named:) like so:

func subtype<T>(of type: T.Type, named name: String) -> (any T.Type)?
1 Like

What advantage do you perceive in Type<P> or Meta<P> over meta P? In that syntax, wouldn't your any Meta<P> just be any meta P?

None, that is an old proposal and I like your idea of the meta keyword a lot more. ;) The syntax is different, but the behavior remains exactly the same.

1 Like
Terms of Service

Privacy Policy

Cookie Policy