Opaque types in collections

Hi everybody, I am sort of hesitant to ask anything about opaque types or protocols
with associated types but I am trying to firm up how I understand this.

In the code below, I make up a protocol with associated type,
then return things that conform to the protocol with an opaque type.
This works. The associated type is constrained to be Numeric, and
I can create structs where the associated type is Int, or the associated type
is Double, and they work OK. This might be oversimplified
because addition has some type conversion magic in it I think, and also because
I'm only using that single operation, but this works as long as I remember that
I don't know anything about the value with the associated type, except that it conforms to Numeric.

If the associated type is Int, I can use things that conform together, and if the
associated type is Float, I can use things that conform together, but if I try to use one of them
with the other, the type checker catches me. Which is good. I don't actually know what
the associated type is, and I can only use methods on Numeric for the values
of the associated type, and that is correct and works.

Two questions:

  1. AdditiveArithmetic provides a static .zero property. (Numeric inherits
    from AdditiveArithmetic). I added a method to my simple protocol below to
    pull it out, because it does not seem like I can call static methods on
    the associated type.

This seems like an oversight, a little bit. If
whatever it is conforms to Numeric, it seems reasonable to be able to
call Numeric's static methods. If I make the .zero property static in
GotANumeric, I can't call it through the associated type either. I can
access Self in code in methods in the struct, but not outside it. Am I
understanding that right?

My explanation, which I guess I am asking if it is correct, is that the
associated types are not exposed to clients, and I think static
methods dispatch directly. So it makes sense that there's no way to call static methods.

But it is not unreasonable
to be able to call static methods on opaque types. In this example the
.zero property would make more sense as a static, but it's
instance-level instead, which looks weird.

  1. I can put things that come from a function returning an opaque type into a collection.
    And then I can do something like reduce over the collection. If I try to put
    inconsistent stuff in the collection, the compiler complains. This is good and works.

My question about this: What is the type of the collection at compile-time?
In the line let arrayOfThings = [x1, x2] below, which compiles and works, I
can't get the compiler to accept a type annotation there. And if I say print(type(of: arrayOfThings))
to get the runtime type I get Array<GotAnInt> which is right at runtime. I feel
weird not being able to put an explicit type annotation there.

I think some of these questions have been asked before, kind of, but there's a lot
of discussion around type stuff. This weekend
got to be a deep dive into a bunch of stuff I did not understand about the type
system.

Thanks!

Code follows:


protocol GotANumeric {
  associatedtype ANumeric : Numeric
  
  var value : ANumeric {get}
  
  /// you need to expose the .zero from the Numeric type explicitly because you can't
  /// get the zero property out of the contained type.
  var zero : ANumeric {get}
}

extension GotANumeric {
  var zero : ANumeric {
    return ANumeric.zero
  }
}
struct GotAnInt : GotANumeric {
  typealias ANumeric =  Int
  let _value: ANumeric
  var value : ANumeric {return _value}
}

struct GotADouble : GotANumeric {
  typealias ANumeric  = Double
  let _value: ANumeric
  var value : ANumeric {return _value}
}

func testGotAnInt() -> some GotANumeric {
  return GotAnInt(_value: 10)
}

func testGotADouble() -> some GotANumeric {
  return GotADouble(_value: 2.7)
}

func arbitraryGotAnInt(_ x : Int) -> some GotANumeric {
  return GotAnInt(_value: x)
}

/// holds a numeric with a generic parameter
struct SomeNumeric<X : GotANumeric> {
  let a : X
}


func testSomeNumerics() {
  let x1  = testGotAnInt()
  let x2 = testGotAnInt()
  
  print(type(of: x1)) // GotAnInt, type(of:) gives the runtime type
  
  let y1 = testGotADouble()
  let y2 = testGotADouble()
  
  let z = SomeNumeric(a: y1)
  
  print(y1.value)
  
  print(y1.value + y2.value)
  
  print(z.a.value)
  
  
  print(x1.value + 1)
  
  print(x1.value + x1.zero)

  let x3 = arbitraryGotAnInt(123)
  print(x3.value + x3.zero)
  
  //print(x1.value + GotAnInt(_value: 123).value) // wants a cast to Int for the first term.
  //print(y1.value + GotAnInt(_value: 123).value) // wants a cast to Int for the first Term.
  
   //print(x1.value + arbitraryGotAnInt(123).value) // Cannot convert value of type '(some CompTests.GotANumeric).ANumeric' (associated type of protocol 'GotANumeric') to expected argument type '(some CompTests.GotANumeric).ANumeric' (associated type of protocol 'GotANumeric') (fair enough)

  // what's the type of this? I think it's [GotAnInt] but it won't compile if
  // I put in an explicit annotation. If I add a y1 in, the compiler gives
  // the heterogeneous type error, so it knows something.
  let arrayOfThings = [x1, x2]
  
  print(type(of: arrayOfThings)) // Array<GotAnInt> at runtime
  
  print(arrayOfThings.reduce(x1.zero) {$0 + $1.value})
  
}
Terms of Service

Privacy Policy

Cookie Policy