[swift-evolution] [Pitch] Improving unspecified generic usability

Logan Shire logan.shire at gmail.com
Tue Aug 8 04:55:10 CDT 2017


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.djura at 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 at 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 <felixcloutier at 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 at swift.org> a écrit :
>>
>>
>> On Aug 7, 2017, at 3:00 PM, Logan Shire via swift-evolution <
>> swift-evolution at 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 at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170808/82adb441/attachment.html>


More information about the swift-evolution mailing list