[Pitch] Improving unspecified generic usability

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]. 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!

···

Logan Shire
iOS @ Lyft

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

···

On Aug 7, 2017, at 3:00 PM, Logan Shire via swift-evolution <swift-evolution@swift.org> 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 <mailto: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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Covariant generic types make for an unsound type system. I believe the reason why Array and Optional are covariant in their generic parameter is that the way their implementation interacts with their internal storage assures that new storage is created if types mismatch: this means that to make covariance work for generic types you have to be extra careful when using references, because it doesn't really make sense from a mathematical standpoint (from my understanding of the matter).

What could probably be useful in your case is the concept of "generalized existential", which is considered in the generics manifesto ( https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generalized-existentials ) and I believe is something that will eventually be added to Swift in the future because many use cases have come up in the mailing list over time: for example smart KeyPaths were implemented with classes instead of structs and protocols because of the lack of generalized existentials in the language.

Elviro

···

Il giorno 08 ago 2017, alle ore 00:00, Logan Shire via swift-evolution <swift-evolution@swift.org> ha scritto:

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]. 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!

Logan Shire
iOS @ Lyft

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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>] = ..."?

Foo<Int> and Foo<Any> are very different. Otherwise, you could take a Foo<Int>, cast it to a Foo<Any> and set a String as its value.

I think what he means are partial generics, e.g: Foo<_>.

···

On 8. Aug 2017, at 04:35, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

On Aug 7, 2017, at 3:00 PM, Logan Shire via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Oh I know, I just couldn't remember if it'd work as long as you didn't mess with the generic bits.

- Dave Sweeris

···

On Aug 8, 2017, at 06:38, Karl Wagner <razielim@gmail.com> wrote:

On 8. Aug 2017, at 04:35, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

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>] = ..."?

Foo<Int> and Foo<Any> are very different. Otherwise, you could take a Foo<Int>, cast it to a Foo<Any> and set a String as its value.

I think what he means are partial generics, e.g: Foo<_>.

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@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

I would like to add something to this discussion on casting generics.

I think there is a temptation to think of generic types as having a
protocol-like aspect. If String conforms to Any, then a [String] ought to
conform to [Any]; the current scope may think of the variable as being a
[Any] even though it is really a [String] and should only accept Strings as
elements. Similarly I shouldn't have to care what the element type is, if
all I want from the array is its count.

I think generic types can be complicated, particularly if they have
multiple generic components. I'm not sure that covariance and
contravariance, if I'm using those terms right, should be implicit for all
generic types.

However, I think it should be possible to explicitly make covariance or
contravariance easier to achieve for certain types.

Practically, what I'm getting at in Swift is this: 'as' and 'as?' are
keywords in Swift, but in a way they're used as operators, and it isn't
possible to write custom implementations.

For the sake of example: I am writing a Stack<Element>. It likely has an
array as a private property but it has other properties as well. I wish to
cast a Stack<String> as a Stack<Any>. I cannot; they're two different
types. The best I can do is write an init function for Stack which takes
another Stack as an argument. The initialiser will have generic constraints
because I'm using a Stack<A> as an argument to produce a Stack<B>. When
this initialiser gets called, the caller will be very clear on what type
they are declaring B to be, so the new Stack can establish its Element type.

However, the compiler would not see a problem in using a Stack<Int> as an
argument to initialising a Stack<String>. I can write code in my
initialiser, testing that each element of type A in the Stack<A> is of type
B, and I can make my initialiser failable or throws if an A not castable to
B is found, but the compiler cannot enforce the generic constraint that A :
B, or B : A. (As of Swift 4, the A : B relationship could mean that A is a
subclass of B, A conforms to B, or both.)

I would be interested in allowing the following function call as a generic
constraint, so that casting generic types can be made easier:

func convert<A, B>(from: Stack<A>) -> Stack<B> where A : B

Is this a possibility?

···

On Tue, Aug 8, 2017 at 2:52 PM, David Sweeris via swift-evolution < swift-evolution@swift.org> wrote:

On Aug 8, 2017, at 06:38, Karl Wagner <razielim@gmail.com> wrote:

On 8. Aug 2017, at 04:35, David Sweeris via swift-evolution < > swift-evolution@swift.org> wrote:

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>] = ..."?

Foo<Int> and Foo<Any> are very different. Otherwise, you could take a
Foo<Int>, cast it to a Foo<Any> and set a String as its value.

I think what he means are partial generics, e.g: Foo<_>.

Oh I know, I just couldn't remember if it'd work as long as you didn't
mess with the generic bits.

- Dave Sweeris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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 <felixcloutier@icloud.com <mailto:felixcloutier@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 <http://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 <mailto:swift-evolution@swift.org>> a écrit :

On Aug 7, 2017, at 3:00 PM, Logan Shire via swift-evolution <swift-evolution@swift.org <mailto: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 <http://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 <mailto: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

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@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 <felixcloutier@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

Terms of Service

Privacy Policy

Cookie Policy