Improving the UI of generics

This isn't what I want though. I want to be able to say (in today's spelling) that T.Type: P and proceed to use T.self anywhere a P constraint is present.

Sure that should be possible if existentials will indeed conform to protocols, then I think it would be a great enhancement to also allow metatypes and their existentials to conform to protocols.

I actually like this direction as it avoids the need for a pseudo generic type. It's a little verbose, but I like the trade-off here. Since the syntax itself overlap we can say that we only need the top three forms, but the super-type relations differ as they preserve todays relations.

Lets say T is a non-protocol type and P is a protocol like in your example already.

any T : any T
meta T : any meta T
any meta T : any meta T

any P : any Any
meta P : any meta Any
any meta P : any meta P

IIRC this exactly the same relationship we had in our proposal. (I hope you understand this super-type notation.)

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

func subtype<T>(of value: T, named name: String) -> (any meta T)?
1 Like

I can't think of a good reason metatypes couldn't fundamentally be plain old generic types. The generics syntax also makes it even clearer (to my eyes) what Meta<any P> vs. any Meta<P> would mean. It'd be difficult to make something that abstracts over existential metatypes and plain old metatypes because they aren't really structurally similar; the former is more a special case of existential types than a proper metatype.


How would this work? static members of a type are instance members of the metatype:

struct MyType {
    static let x = 42
    static func doIt() {}
let myMeta: MyType.Type = MyType.self

Are you imagining that the static members would be implemented something like a conditional extension like this:

extension Meta where Concrete == MyType {
   let x = 42 // instance stored properties aren't normally allowed in extensions...
   func doIt() {}

Even if this approach works wouldn't it be limiting? Now Meta can only have a single conformance to a protocol for all concrete types. My mental model is of metatypes being a lot more first class than this. Each metatype should be able to have its own set of protocol conformances, shouldn't it?

I think you did a correct observation here, even if we had Meta<T>, it would probably be a generic keyword type similar to non-generic Any as keyword type. That implies it won‘t be a citizen of stdlib directly.

My conclusion is:

  • I hope we can move away from T.Type and reclaim .Type
  • With this we could clean up metatypes drastically.
  • We would communicate their behavior more clearly.
  • We would get a new set of constraints we could express in Swift‘s type sytem.

If it’s not a normal generic type or typealias I think this syntax would be confusing. If we had type-level functions and used the same angle bracket syntax for those then it would make a little bit more sense. It still isn’t clear to me how we would be able to extend metatypes to add protocol conformances to them though. I don’t think we would be able to extend the result of a type function.

So you can argue about Collection<.Element == T>, no? As Joe said, for some any Meta<T> is visually more appealing than any mata T.

We aren’t taking about supporting Collection<.Element == T>, we are talking about requiring existentials and opaque types to be prefixed with any and some respectively so it would be any Collection<.Element == T> and some Collection<.Element == T>. The prefix keywords make it clear that the angle brackets have different meaning than they do for ordinary generic types.

I’m not trying to fixate on syntax here. I’m trying to focus on semantics. Ideally the syntax would align with the semantics so I’m asking questions where I see syntax that doesn’t seem to align with the semantics I hope to see.


Okay I understand. So in case you are fine with meta T then what would you think about typealias Meta<T> = meta T a small convenience? (Sure this is duplication of the same thing, but that's not my question.)

That would be a way to make the Meta<T> syntax fit into the semantic model I hope to see. If it didn’t conflict with the Any we already have you could also imagine typealias Any<T> = any T to access the associated existential of any type.

1 Like

Given the naming discusses just above the quote, the natural way to do constraints is:

func foo(a: some Collection A, b: some Collection B) -> some Collection C
  where A.Element == B.Element, B.Element == C.Element

No need for new weirder syntaxes.


I understand using shorthands (anonymous types) for situations where they are well suited for. But for more complex situations, named types are natural fit. And anonymous types can become unwieldy or difficult when you try to refer to them without having something exact to refer to.

I mean is it really that critical to create new, unnatural syntax for anonymous types that covers every single case of complexity that would be already possible with named types (who require nothing of this)? Is it worth making the parser/compiler that more complex? Having other people look at shorthand code and not understand it? Wouldn't it be ok to say that "anonymous types cover these simple cases as a subset and full functionality is provided with named types"?

Any kind of shorthand we come up with for some in return type position, should be mirrored (where applicable) for parameter position, as well as for any in same places. And we don't want to end up with a jungle of syntax.

1 Like

The way I would like to see the evolution of any/some is that after MVP:s like the latest SE-0244: Opaque Result Types (reopened) - #22 by Moximillian, the named types would be introduced (at some point): some Collection C, maybe any Protocol A, to handle all the complexity with constraints and others, which comes with no requirement for new syntax for setting up constraint rules etc.This gets us full functionality.

Once that is in use and people see actual code, then introduce some well-chosen shorthands for anonymous types, where they make sense. After all, at that point it's mostly just eye-candy, and we can have a little bit patience for one's favorite sugar, right?

EDIT: hmm, if named some types would be used e.g. in stdlib/Foundation, would those names be visible to users of library? If that’s a problem then maybe initial implementation of named some types could be converted to anonymous by the compiler behind the scenes so that names are not visible to outside of module... Ultimately, since there’s been discussion about figuring ways to refer to anonymous types too, I’m not sure such conversion would be worth it though. You’re not really hiding anything, just making it more difficult to use it?

We do have precedent with regular generics func foo<T: P>() of having always to name the type. And we have survived quite well without anonymous types there. While some is bit different when used as reverse generic (return type), I think naming is still right place to start. If I understand correctly, the current existential syntax func foo(item: Protocol) is effectively anonymous type equivalent and people are struggling with that concept and inability to refer to the exact named thing.

In terms of ease of use, here’s a bit exaggarated example:

func foo(item: Protocol)
— Error: ”protocol Protocol does not conform to protocol Protocol”

with named any
func foo(item: any Protocol I)
— Error: ”existential I does not conform to protocol Protocol”

1 Like

(Feel free to correct my statements everyone; I am after all just a self proclaimed compiler enthusiast)

This is at least partially because objective c, in the cases where it suspects it will be given a type, will inline assumptions and even the code corresponding to the call if the value given is of the expected type. This is typically where you start seeing advantages from profile-based optimizations, but even then your generic algorithms may suffer since they are called with a variety of types.

The overhead of dynamic dispatch is often not just in the pointer lookups, but in killing the ability to optimize the code across the call boundary, such as by inlining or elimination of code paths.

Objective-C never inline any method invocation.
Objective-C does not support final classes, and even if it support it in the languages, any class may be subclassed at runtime (see KeyValueObserving for instance). So, inline a method call would break languages assumptions.

Sure. I think this case falls well below the threshold of a "more complex situation", though, because A, B, and Result don't have any real identity beyond "the type of a/b/the return value", and so being able to refer to them directly as such strikes me as a lower-complexity state; you have fewer names, and it is straightforward to refer to the unnamed things in terms of named ones. If there were multiple arguments or returns involving the same type, that's the point at which I think it'd become useful to name the type independently.

Similarly, referring to a protocol with some of its associated types constrained is such a common thing that it seems unfortunate to me that today it forces you into a situation where you need to introduce a where clause, which is itself a pretty big complexity step up from applying constraints to type parameters, and need to use a notation that relies on naming type parameters to describe the constraint.


Yes, for simple cases shorthands are fine, a return keyword might be fine for it.

But in addition to shorthands consider those people who are not so intimately familiar with these relatively advanced concepts and syntax.

People have learned generics, it has named types, it’s maybe more similar to several other languages, and people have learned to use conformances, joined conformances and where clauses with it.

Now they try to use existentials, they are bit similar, except they don’t have names (really), and people mistake the Protocol for its name. But the where clauses are same, so some parts are familiar.

then we have some. Sure simple cases are fine. But if you go into situations similar to where clauses you’re confused again, just like with existentials.

The more I think of this It feels like fundamental flaw in swift syntax design (especially in protocols), and all the more important that both existentials any and reverse generics some should have named type syntax for ease of use, as well as, for handling complexity reasons. This is irrespective of whether there is a shorthand syntax for them.

And by named type syntax I refer to the one mentioned before:
any Protocol A
some Protocol S

1 Like

I don't necessarily agree with this. Avoiding names requires language magic that can be avoided with sugar that makes it concise to provide names directly in the argument list. I'm not necessarily opposed to the magic if people really want it, but I don't think it's a substitute for being able to name generic parameters directly in the argument list. One of the first things I requested (via radar) when Swift was first announced was the ability to introduce and name generic parameters directly in the argument list (avoiding angle brackets). The some Collection C syntax does a good job of fulfilling that request.

Big +1 to this. The indirection necessary when constraints only apply to one argument is really unfortunate.

I agree with Joe that names should not be required. They are often not necessary at all. But when they are it should be possible to introduce them without using angle brackets. There should be progressive disclosure of complexity in the syntax that matches the progressive increase in complexity of the semantics. As has been discussed abundantly, angle brackets introduce indirection that exceeds inline syntax and is usually unnecessary.


Angle brackets are more appropriate for shortcut syntax if anything. Updated my post with what syntax I was thinking regarding named types.