Hi all, I'm trying to figure out the best way to constrain an associated type of an [any P] in order to enable additional functionality and I'm not sure of the best way to do this. Here is a very simplified example using stdlib types:
protocol P {
associatedtype T: Numeric
associatedtype S: StringProtocol
var number: T { get }
var string: S { get }
}
extension P {
func example1() -> T.Magnitude {
number.magnitude
}
}
extension P where T: FloatingPoint {
func example2() -> T.Exponent {
number.exponent
}
}
struct S1: P {
var number: Double
let string = "S1"
}
struct S2: P {
var number: Int
let string = "S2"
}
struct S3: P {
var number: Float
let string = "S3"
}
let arrayOfP: [any P] = [ // if I can constrain P.T: FloatingPoint, example2() should become available, but how?
S1(number: 5.5),
S3(number: 1.3),
S1(number: 2.0)
]
let s1 = S1(number: 4.2).example2()
let s3 = S3(number: 2.3).example2()
arrayOfP.forEach {
$0.example1()
$0.example2() // how to get this to work?
}
xwu
(Xiaodi Wu)
2
The most straightforward way to do this (assuming that you want exactly what you say, in which case a primary associated type would be insufficient) would be to declare a refining protocol Q: P where T: FloatingPoint, conform the relevant types to Q, and then make your array of type [any Q].
3 Likes
Thanks! I thought about that, but was hoping for something that didn't require types (of which I may not control) to remember to conform to Q if number is a FloatingPoint. I'll go this route if nothing better comes along... 
xwu
(Xiaodi Wu)
4
If you control Q, it’s legitimate to conform any type you encounter retroactively to Q (as long as the requirements are met of course).
2 Likes
But only if I have the original type to allow a conformance. (thinking through edge cases right now) If I have a library where the original type might pass unknown through an api as an any. For example say the api is this:
func callFuncsOnP(_ p: any P)
I can unbox that and call example1(), but there still isn't a way to call example2() that I can see. I can make sure that number is a FloatingPoint by casting, but compiler won't propagate that knowledge to the type as far as I can tell.
Your situation sounds nightmarish, but I think it will be improved once the language evolves to the point where type erasure isn't necessary past one opaquing level.
let anySequence: AnySequence<some FloatingPoint> = .init([0])
// …should be replaceable by the following, but it won't compile yet.
// 'some' types are only permitted in properties, subscripts, and functions
let someSequence: some Sequence<some FloatingPoint> = [0]
I think that the overall solution would be the ability to add where clauses to generics (type or protocol) at any point of use. So:
var a: (any P where P.T: FloatingPoint) // compiler can prevent other any P assignments and has more type info
if let a = a as? (some P where P.T: FloatingPoint) {
// compiler has more type info
}
struct S<T: Numeric> {
let number: T
}
let s = S(number: 1.0)
if s is (S where T: FloatingPoint) {
// compiler has more type info and we can now use number as a FloatingPoint as well
}
I'm not sure how difficult this change would be since I haven't looked at what type info the ABI is already encoding. If it already has this information available, then it is just a matter of adding syntax. Otherwise, Swift 6! 
2 Likes
@xwu do you have any idea of how difficult this change would be?
xwu
(Xiaodi Wu)
9
Extremely difficult, a full-fledged feat of engineering.