Generic parameters in "as?" checks


(Charles Srstka) #1

Currently, generic types can be hard to work with in certain cases; particularly if you want to check if an object belongs to a type that has generic parameters.

I’m wondering if it would be completely unreasonable to ask for the ability to introduce a generic parameter into an as? check, and have it treated the way a generic type would be treated in a generic function. Using Array as a convenient generic type that’s easy to create, let’s suppose we want to check if some random object is a array containing some type that we don’t care what type it is, as long as it’s a subclass of some type, conforms to some protocol, etc.

Something like this:

class Foo {
  required init() {}
  func doAFooThing() {}
}
class Bar: Foo {
  required init() { super.init() }
  func doABarThing() {}
}

if let arr = someObject as? Array<T: Foo> {
  for eachFoo in arr {
    T.doAFooThing()
    // but T.doABarThing() would not work
       }
  
  array.append(T())
}

Or perhaps, if we need angle brackets to be somewhere, something like this:

if let arr = someObject as? <T: Foo> Array<T> {
  …
}

Is this a feasible thing to ask for?

(Apologies if there’s already a way to do this; I was looking for one without success.)

Charles


Conditionally further constrain a generic parameter: if <T: Constrained> { ... }
(Slava Pestov) #2

Here’s an attempt to implement this with existing language features:

protocol HasElementType {
  func getElement() -> Any.Type?
}

extension HasElementType {
  func getElement() -> Any.Type? {
    return nil
  }
}

extension Array : HasElementType {
  func getElement() -> Any.Type? {
    return Element.self
  }
}

func isArrayWithElementType<T>(a: Any, _ m: T.Type) -> Bool {
  guard let aa = a as? HasElementType else { return false }
  guard let elt = aa.getElement() else { return false }
  return elt is T.Type
}

protocol P {}
struct X : P {}

print(isArrayWithElementType([1,2,3,4,5], Int.self))
print(isArrayWithElementType([1,2,3,4,5], Float.self))
print(isArrayWithElementType(["a", "b"], String.self))
print(isArrayWithElementType([X()], X.self))
print(isArrayWithElementType([X()], P.self))

However, the limitation here is that when we do ‘elt is T.Type’, if elt is a metatype conforming to a protocol P, and T is P, the ‘is’ check just checks for metatype identity and not conformance.

So if a generic parameter T is bound to a concrete type C, then T.Type means C.Type. However, if C is a protocol type, then T.Type is really C.Protocol, not C.Type.

Here’s a silly idea:

- Switch P.Type and P.Protocol syntax where P is a protocol type, so that P.Type is the type of P.self, and P.Protocol is the existential metatype. This makes ‘P.Type’ consistent with ’T.Type’ where T is bound to a protocol type. It also makes ‘P.Protocol’ kind of like a protocol — types that conform to P have metatypes that conform to P.Protocol.

- Add a ‘: protocol’ generic constraint, asserting that the type parameter is some protocol existential type:

  func isArrayWithElementType<T : protocol>(…)

- For a generic type parameter T with an existential constraint, you would be able to write T.Protocol to refer to the existential metatype. T.self would not be an instance of T.Protocol, but the elements of your array that *conform* to T would be instances of T.Protocol.

Maybe also we want to allow casts with a metatype expression, rather than a type identifier, on the RHS.

Or perhaps, if we need angle brackets to be somewhere, something like this:

if let arr = someObject as? <T: Foo> Array<T> {

Yeah, it’s going to have to be this, since <T : Foo> is shorthand for <T where T : Foo>, and in general the <> of a bound generic type reference are different from <> in a generic signature — the latter introduces new type variables for instance.

  …
}

Is this a feasible thing to ask for?

I think so, but we have to careful with the design. This ties in with some of the discussions on generalizing existential types, also.

···

(Apologies if there’s already a way to do this; I was looking for one without success.)

Charles

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


(Charles Srstka) #3

Here’s an attempt to implement this with existing language features:

protocol HasElementType {
  func getElement() -> Any.Type?
}

extension HasElementType {
  func getElement() -> Any.Type? {
    return nil
  }
}

extension Array : HasElementType {
  func getElement() -> Any.Type? {
    return Element.self
  }
}

func isArrayWithElementType<T>(a: Any, _ m: T.Type) -> Bool {
  guard let aa = a as? HasElementType else { return false }
  guard let elt = aa.getElement() else { return false }
  return elt is T.Type
}

protocol P {}
struct X : P {}

print(isArrayWithElementType([1,2,3,4,5], Int.self))
print(isArrayWithElementType([1,2,3,4,5], Float.self))
print(isArrayWithElementType(["a", "b"], String.self))
print(isArrayWithElementType([X()], X.self))
print(isArrayWithElementType([X()], P.self))

Thanks for that. The trouble is that the problem I’m trying to solve is actually more complicated than the example; it’s not actually Array that I’m dealing with, but a custom type. I am looking for a way not just to see whether the thing contains a certain type, but actually to cast it to the generic type with ‘as?’ so that I can use it as the generic type, pass it to functions that want that generic type, etc., do things with its type T that require something somewhere knowing what the type is (in my example, I used arr.append(T()), an operation that needs to know what type T is, so that it can create the instance. Just creating a Foo() wouldn’t work, since the array could be of some Foo subclass and not take garden-variety Foos.

If we want to get *really* specific, I’m actually dealing with a protocol with associated types, not a generic struct. However, they both fall under the same umbrella of “need to use this as a generic constraint”, and “generic” is less obnoxious to type over and over when discussing things than “protocol with associated types”. :stuck_out_tongue:

if let arr = someObject as? <T: Foo> Array<T> {

Yeah, it’s going to have to be this, since <T : Foo> is shorthand for <T where T : Foo>, and in general the <> of a bound generic type reference are different from <> in a generic signature — the latter introduces new type variables for instance.

Okay, in the example above, that leaves five places for the generic signature to go: after the “if”, after the “let”, after the variable name, after the equals sign, and after the “as?”. Or perhaps we could introduce some sort of new keyword in there to which to attach the generic signature.

Is this a feasible thing to ask for?

I think so, but we have to careful with the design. This ties in with some of the discussions on generalizing existential types, also.

That’s good to hear. Is there any information on what the Swift team is planning to do with existentials?

Charles

···

On Jan 20, 2016, at 11:26 PM, Slava Pestov <spestov@apple.com> wrote:


(Slava Pestov) #4

If we want to get *really* specific, I’m actually dealing with a protocol with associated types, not a generic struct. However, they both fall under the same umbrella of “need to use this as a generic constraint”, and “generic” is less obnoxious to type over and over when discussing things than “protocol with associated types”. :stuck_out_tongue:

I think this falls under the “generalizing existentials to support protocols with associated types” bucket.

Okay, in the example above, that leaves five places for the generic signature to go: after the “if”, after the “let”, after the variable name, after the equals sign, and after the “as?”. Or perhaps we could introduce some sort of new keyword in there to which to attach the generic signature.

Various approaches have been discussed before. The one that’s closest to what you describe above is to have some kind of ‘open’ keyword,

func foo(p: SomeProto) {
  let t = open p as T {
    T.SomeAssocType.someStaticMethod(…)
  }
}

But another promising approach is path-dependent types, so that above you could write p.SomeAssocType for instance.

Is this a feasible thing to ask for?

I think so, but we have to careful with the design. This ties in with some of the discussions on generalizing existential types, also.

That’s good to hear. Is there any information on what the Swift team is planning to do with existentials?

Oh, I was just referring to the discussion on this list. No concrete proposals have been accepted so far, AFAIK.

Cheers,

Slava

···

On Jan 20, 2016, at 9:53 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

Charles


(Charles Srstka) #5

Okay, in the example above, that leaves five places for the generic signature to go: after the “if”, after the “let”, after the variable name, after the equals sign, and after the “as?”. Or perhaps we could introduce some sort of new keyword in there to which to attach the generic signature.

Various approaches have been discussed before. The one that’s closest to what you describe above is to have some kind of ‘open’ keyword,

func foo(p: SomeProto) {
  let t = open p as T {
    T.SomeAssocType.someStaticMethod(…)
  }
}

Thanks, this looks good. Based on this, I found the earlier discussion where this came up, but it seems that the example above assumes you already know p is SomeProto. I assume there’d be a way to take p as something else, like an Any, and dynamically cast it to SomeProto using some combination of as? and open, right?

Is this a feasible thing to ask for?

I think so, but we have to careful with the design. This ties in with some of the discussions on generalizing existential types, also.

That’s good to hear. Is there any information on what the Swift team is planning to do with existentials?

Oh, I was just referring to the discussion on this list. No concrete proposals have been accepted so far, AFAIK.

Well, if someone proposes one, it’ll get a +1 from me.

Thanks,
Charles

···

On Jan 20, 2016, at 11:56 PM, Slava Pestov <spestov@apple.com> wrote:


(David Turnbull) #6

This sounds like it could lead down a path that would clean up some ugly
code I wrote recently.

public protocol VectorType : GLmathType {
    typealias FloatVector
    typealias DoubleVector
    typealias Int32Vector
    typealias UInt32Vector
    typealias BooleanVector

This is the protocol for vector2/3/4 types. Some functions need to return a
bool result. Others are an unsafe cast to another type. I'm also using the
BooleanVector as a key where I need vectors of the same length which have
different elements.

public func notEqual<genType:ScalarVectorType where
    genType.BooleanVector.Element == Bool,
    genType.BooleanVector == genType.BooleanVector.BooleanVector
    >(x:genType, _ y:genType) -> genType.BooleanVector {
        return genType.BooleanVector(x, y, !=)
}

Many times I went looking for any way to get back to the raw generic type.
I don't know if this makes sense out of context, so here's the full set of
prototypes:
https://github.com/AE9RB/SwiftGL/blob/master/Sources/SwiftGLmath/Types.swift

I hope this is helpful to those of you thinking about changes. It's really
far down on my wish list though. The "expression too complex" errors and
compile times are blocking me right now.

-david

···

On Wed, Jan 20, 2016 at 10:21 PM, Charles Srstka via swift-evolution < swift-evolution@swift.org> wrote:

On Jan 20, 2016, at 11:56 PM, Slava Pestov <spestov@apple.com> wrote:

Okay, in the example above, that leaves five places for the generic
signature to go: after the “if”, after the “let”, after the variable name,
after the equals sign, and after the “as?”. Or perhaps we could introduce
some sort of new keyword in there to which to attach the generic signature.

Various approaches have been discussed before. The one that’s closest to
what you describe above is to have some kind of ‘open’ keyword,

func foo(p: SomeProto) {
  let t = open p as T {
    T.SomeAssocType.someStaticMethod(…)
  }
}

Thanks, this looks good. Based on this, I found the earlier discussion
where this came up, but it seems that the example above assumes you already
know p is SomeProto. I assume there’d be a way to take p as something else,
like an Any, and dynamically cast it to SomeProto using some combination of
as? and open, right?

Is this a feasible thing to ask for?

I think so, but we have to careful with the design. This ties in with some
of the discussions on generalizing existential types, also.

That’s good to hear. Is there any information on what the Swift team is
planning to do with existentials?

Oh, I was just referring to the discussion on this list. No concrete
proposals have been accepted so far, AFAIK.

Well, if someone proposes one, it’ll get a +1 from me.

Thanks,
Charles

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