Possibility to makes functions `required`

Non of that features solve the full set of issues you can solve with generic protocols. One of that is multiple conformances to the same generic protocol but with different generic paramaters. PATs cannot and will not solve that issue, nor will generalized existentials or opaque types. To be able to truly go out generically, the language must allow us to express such generic features, but right now it doesn't, which lead to a lot of duplicate non-generic code.

I cannot provide you concrete examples here, since this feature is not supported and such code simply does not exist in my code base, because I cannot write it and had to fall back to other techniques and bend the compiler as much as it currently allows me to.

That is true in general, but that is not the point I was making. By private storage I meant that you don't have to expose it at all, not even for sub-classes. You cannot not do this with protocols right now, and not sure if we ever will be able to do that.

Could you please clarify then how would "abstract classes" solve these issues? Are you proposing to introduce multiple inheritance together with "abstract classes"? Or what do you mean here by "abstract types"? How would syntax for those look and how those would work with multiple inheritance?

If the storage is completely private then implementors of the protocol don't need to access it, right?

But I guess I see what you mean here. Not sure this is related to what we've discussed here in any way. It looks more like allowing users to define storage in extensions, which is currently discussed in a separate topic.

Sorry but you have missed my point there. You were talking about abstract classes AND about generic protocols, I replied explicitly only about generic protocols. In that sense I cannot answer these questions since it's clearly would be non-sense to claim that abstract classes can solve these issues. Multiple conformances were meant in regard to generic protocols, which is a different topic and has nothing to do with abstract classes. I'm not sure how it ended up in this thread. That said, sorry if you got confused by my reponse, but I really meant that PATs/generialized existentials/opaque types cannot solve the full set of issues that generic protocols can and vice versa. PATs and GPs overlap, that is a fact, but they also have distinct areas that only that core feature can solve. I hope we can cool down in that regard. ;)

I understand, I've listed generic class and protocols because they clearly seem to be equivalent for use cases working around PAT issues that you've listed here:

I hope I can agree with that, but did I miss anything that clearly describes those distinct areas for GPs? Any concrete practical examples of code that are currently implemented in a way that won't benefit from improved PATs (GE, opaque etc) but will only benefit from GPs? I'm genuinely interested in such examples so that I can better understand potential paths for the evolution of Swift's type system.

Please scroll up a few posts and check out the GitHub gist I linked. It contains potential examples for GPs.

FWIW, I think we may actually get generic protocols. One thing that I think we all really want in Swift is for protocols to be nest-able and to have nested types, but that creates a problem when generics/associated types get involved. They are very similar systems but there are some subtle differences and they are not actually related at the language level.

Take a trivial example:

class MyGenericComponent<T> {
  protocol Delegate {
    func itemIsReady(_ item: T)
  weak var delegate: Delegate?

Is MyGenericComponent<Int>.Delegate the same as MyGenericComponent<String>.Delegate?

This is one of the differences between generics and associated types: different parameterised versions of a generic type are distinct, but protocol conformances with different associated types are all conformances to the same protocol.

So if we transform the captured generic parameter in to an associated type, both spellings would reference the same protocol, and a single type could not conform to both of them at once:

// If we turn the captured generic parameter in to an associated type,
// the compiler would roughly consider it as equivalent to this:

class MyGenericComponent<T> {
  weak var delegate: (MyGenericCompnent_Delegate where .T == Self.T)?

protocol MyGenericComponent_Delegate {
  associatedtype T
  func itemIsReady(_ item: T)

extension MyDelegate: MyGenericComponent<Int>.Delegate { /* ... */ }

// Error: cannot conform to MyGenericComponent.Delegate more than once.
extension MyDelegate: MyGenericComponent<String>.Delegate { /* ... */ }

This feels weird because, as mentioned above, MyGenericComponent<Int> and MyGenericComponent<String> are otherwise treated as entirely distinct in the language. The most natural solution would be to make the protocol generic.

1 Like

Small typo here, you missed : AnyObject in order to be weak, but I agree with the rest.

I also think that we need a way to express both things you mention here even when the types are nested. Sure one can emulate on of these things with a type alias today, but I would prefer to keep the nested namespace without moving the shared type outside the parent type. I think this issue came up in a different topic about type members, but in our case I would like a way to express shared types that are nested and not linked through a type alias.

@thedjnivek sorry that this topic derailed so fast, we should continue focusing on the original issue and/or abstract classes.

(emphasis mine)

PATs and GPs are completely orthogonal in my book. (As are generalized existentials.)

PATs define "outputs" of a type/protocol, while the parameters of a GP defines its "inputs". They serve completely different purposes and actually work great together!

As such the associatedtype of a PAT is to protocols what typealias is to a struct/enum/class.
And the <T, …> of a GP is to protocols what <T, …> is to a (generic) struct/enum/class.

So just like <T, …> serves a different purpose than typealias T = … in a struct/enum/class it serves a different purpose for PATs/GPs.

I think what makes things confusing to many people is the fact that Swift implicitly turns any <T> into a typealias T = T (if that was a thing), i.e. it's exposing (private) generic arguments as (public) typealiases. I really wish Swift wouldn't do this, but would give me control over what I'm exposing.

To give a concrete motivation, as far as I'm informed the swift team doesn't have a coherent and universal story for how we're gonna solve type-safe, compiler-checked type-conversions that one can generalize over.

I.e. there is no way to write something like this in Swift:

func doSomething<T, U>(input: T) -> U where T: ConvertibleInto<U> {
    return input.into()

This obviously is a contrived example, so just imagine more useful stuff going on in func doSomething. It's the fact that such semantic (on a type level) is not expressible in Swift, that's bugging me every other day. It forces you to tightly couple your libraries to specific types.

If you want to write a generic low-level framework that does any kind of arithmetic (without restricting it to specific types of integer/floating point), you very quickly reach the boundaries of Swift.

1 Like

I think this is wrong signature there, instead it should be either, ConvertibleFrom but that would likely involve an init instead of into method, or T: ConvertibleInto<U>.

You're right. Fixed.

You could have it be either where T: ConvertibleInto<U> or where U: ConvertibleFrom<T>.

In Rust for every pair of types T and U where U: ConvertibleFrom<T> you automatically get a derived T: ConvertibleInto<U>, which allows you to nicely call the type-inferred t.into(), instead of having to do an explicit U(t).

Thank you for the detailed explanation @regexident!

Interesting that I don't see similar discussions in the relatively recent Vector Manifesto topic, although it does mention a need for making generics in Swift more flexible.

I find it quite useful to look at the experience of other languages. How do you find it, does Rust manage well the presence of both traits with associated types and generic traits?

Also looking at Haskell, which arguably has the state of the art type system, I always thought that type classes directly correspond to PATs, but probably I was wrong. After another look it really seems that type classes are more like generic protocols, and actually type families are much more similar to PATs than plain type classes.

Vectors as understood in that document are always the concrete Swift integer types (UInt8, Int8, UInt16, Int16, … , UInt64, Int64, Float, Double), because they are constrained to SIMDScalar. If they were not, there would be no performance benefit to using vector types. Similarly, it’s a meaningless question to ask what if they had generic math operations like log, as we don’t have a way of evaluating them without going to libm which only takes concrete Float and Double types.

1 Like
Terms of Service

Privacy Policy

Cookie Policy