I see what you're saying, and I agree that this is more of a half-measure. But the benefit of this approach is that it's pretty trivial to implement, and the language features it introduces could be reimplemented with existentialists when they become available. I think advancing the syntax of the language to improve usability in the near term is worthwhile. If this syntax would not be supported by the existential approach I think that would be a different story.
Also, I think this could go hand-in-hand with a syntax for types that are protocols with their associated type fulfilled, e.g. let grassEater = animal as? AnimalProtocol where Food = Grass or: let grassEater = animal as? AnimalProtocol<Food: Grass> These grassEater values could then be used just like any other value. On Tue, Aug 8, 2017 at 11:14 AM Elviro Rocca <retired.hunter.dj...@gmail.com> wrote: > To my understanding, the following is exactly as it should be: > > FooStruct<String> as? FooStruct<Any> // Compiles but conversion fails, > becomes nil, and that's normal > > The reason for this is that FooStruct<String> is not a subtype of > FooStruct<Any> (or just FooStruct), while String is of course a subtype of > Any, because generic types are not covariant in Swift, and that's the way > it should be for a sound static type system. My comments on this were > related to what you wrote about arrays. > > In theory a protocol without associated types could be synthesized for all > the non generic properties and methods of a generic type, with the ability > of casting to it and possibly from it. > > It's a useful idea, and I'm all for it (I think literally everyone that > uses generics and protocols with associated types encountered these kinds > of problems at least once), I'm just saying that I'd rather work on > generalized existentials which have already been considered by the core > team, at least from a theoretical standpoint, have a greater scope and > include cases like this. In general I tend to prefer broader, more generic > solutions rooted in type theory when dealing with generics and protocols, > but that's just me. > > > Elviro > > > Il giorno 08 ago 2017, alle ore 10:44, Logan Shire via swift-evolution < > swift-evolution@swift.org> ha scritto: > > Thanks for the feedback! > > Félix, sorry about the confusion between FooStruct and FooProtocol - I'll > refer to them as such moving forwards. > > David, I don't believe you should be able to cast an [FooStruct<String>] > to an [FooStruct<Any>] because those are both valid specifications. If > Generalized > Existentials > <https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generalized-existentials> > are > implemented, that would be another story, but that's outside the scope of > this proposal. I do believe you should be able to cast [FooStruct<String>] > to [FooStruct], and that you should be able to flatMap [FooStruct] into > [FooStruct<Any>] with as?, but all of the casts would fail and you would be > left with an empty array. > > In regards to the Named protocol, yes, that is the current idiomatic > approach to solving this problem (along with making a function > unnecessarily generic and then using the generic type as a constraint). I > want to avoid jumping through those hoops. We'd essentially be synthesizing > the Named protocol with the same name as the non-generic version of the > type. I.e. FooStruct<T>: FooStruct > > Félix, I believe the above answers some of your questions, but in regards > to protocols with associated types, I'd imagine it would work the same way. > If FooProtocol has an associated type T, there would be another protocol, > FooProtocol, without the associated type. (behind the scenes its garbled > name would be different) > > Also, an aside, it would be nice if protocols could use the generic syntax > for their associated type constraints. I.e. "FooProtocol with T = Int" > could be expressed as FooProtocol<T: Int>. It feels strange that we have > two different syntaxes for essentially the same language construct. At the > very least, I want some way to cast a value to a protocol type with an > associated value. E.g. "if let grassEater = any as? Animal where Food = > Grass" > > Elviro, yes, the generalized existentials would help a lot here, but > that's outside the scope of what I'm proposing. In the near term I'd like > to be able to use a generic type's non-generic interface, casting to and > from it. See the above discussion regarding the Named protocol. Essentially > we'd be synthesizing the Named protocol, but where the type's name is the > same as the non-generic version of the type name. > > FooStruct<String> as FooStruct // works > FooStruct as? FooStruct<String> // works > FooStruct as? FooStruct<Any> // Compiles but conversion fails, becomes nil > FooStruct<String> as? FooStruct<Any> // Compiles but conversion fails, > becomes nil > > Let me know if you have any other questions! > > Logan > > > On Tue, Aug 8, 2017 at 9:43 AM Félix Cloutier <felixclout...@icloud.com> > wrote: > >> I'm going to separate your examples into FooStruct and FooProtocol for >> clarity. >> >> I agree that generics tend to propagate virally and I remember that at >> some point I wanted type erasure, though I don't remember for what exactly. >> The solution for `sayHi`, right now, is to make that one generic too: >> >> func sayHi<T>(to foo: T) where T: FooProtocol { >> print("hi \(foo.name)") >> } >> >> >> The "let foos: [FooStruct] = [FooStruct(name: "Int", >> value: 2), FooStruct(name: "Double", value: 2.0)]" part can't work for >> structs because arrays require each element to have the same size (but it >> could work for classes). >> >> Even then, you couldn't infer the type to [FooClass<Any>] because >> contravariance isn't permissible in that situation: doing so would allow >> you to assign any Any to a FooClass's value. >> >> Another problem that this would have to solve is that once you lose the >> associatedtype that came with the protocol, there is nothing you can do to >> recover it; you currently can't express "FooProtocol with T = Int" as a >> type that you can cast to, so you would only be able to pass the instance >> to functions that don't have constraints on T. >> >> But all in all, with my current understanding of the issue, I think that >> I'm favorable to the idea. >> >> Félix >> >> Le 7 août 2017 à 19:35, David Sweeris via swift-evolution < >> swift-evolution@swift.org> a écrit : >> >> >> On Aug 7, 2017, at 3:00 PM, Logan Shire via swift-evolution < >> swift-evolution@swift.org> wrote: >> >> One of my longstanding frustrations with generic types and protocols has >> been how hard it is to work with them when their type is unspecified. >> Often I find myself wishing that I could write a function that takes a >> generic type or protocol as a parameter, but doesn’t care what its generic >> type is. >> >> For example, if I have a type: >> >> struct Foo<T> { >> let name: String >> let value: T >> } >> >> or: >> >> protocol Foo { >> associatedtype T >> var name: String { get } >> var value: T { get } >> } >> >> And I want to write a function that only cares about Foo.name, I’d like >> to be able to: >> >> func sayHi(to foo: Foo) { >> print("hi \(foo.name)") >> } >> >> But instead I get the error, “Reference to generic type Foo requires >> arguments in <…>” >> >> Also, when you want to have a polymorphic array of generic types, you >> can’t: >> >> let foos: [Foo] = [Foo(name: "Int", value: 2), Foo(name: "Double", >> value: 2.0)] >> >> And if you remove the explicit type coercion, you just get [Any] >> >> let foos = [Foo(name: "Int", value: 2), Foo(name: "Double", value: 2.0)] >> >> I wish that could be inferred to be [Foo]. >> >> >> What happens if you try to say "foos: [Foo<Any>] = ..."? >> >> >> >> I’d like to propose being able to use the non-generic interface of a type >> normally. >> I.e. if you have a type Foo<T>, it is implicitly of type Foo as well. The >> type Foo could be used like any other type. >> It could be a parameter in a function, a variable, or even the generic >> type of another type (like a Dictionary<String, Foo>) >> >> The only restriction is that if you want to call or access, directly or >> indirectly, a function or member that requires the generic type, >> the generic type would have to be known at that point. >> >> Foo<T> should be able to be implicitly casted to Foo wherever you want, >> and Foo could be cast to Foo<T> conditionally. >> Initializers would still obviously have to know the generic type, but >> given the above example, you should be able to: >> >> let names = foos.map { $0.name } >> >> However, you could not do the following: >> >> let foos = [Foo]() >> >> Because the initializer would need to know the generic type in order to >> allocate the memory. >> >> Let me know what you think! >> >> >> The idiomatic solution would be to create a `Named` protocol with a `var >> name: String {get}` property, and write your function like `func sayHi(to >> foo:Named) {...}`. However, this `Named`protocol is really pretty trivial >> -- its purpose is simply to "degenericify" a generic type, not to provide >> any semantic meaning. Perhaps an analogy could be drawn between such >> "trivial protocols" and how we sometimes view tuples as "trivial structs"? >> Dunno, maybe I'm just trying to turn two trees into a forest, but this >> kinda smells like it might be part of a bigger issue, and if it is I'd >> rather tackle that and then see if we still need to address anything here. >> >> +1, either way, though. >> >> - Dave Sweeris >> >> _______________________________________________ >> swift-evolution mailing list >> swift-evolution@swift.org >> https://lists.swift.org/mailman/listinfo/swift-evolution >> >> >> _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution > > >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution