This problem arises in a SwiftUI thing, but my question is about how existentials are supposed to work.
Given this code
struct DiceBox: View {
var lastRoll: Dice_Roll? = nil
var body: some View {
lastRoll != nil ? RollView(roll: lastRoll!) : EmptyView()
}
}
The compiler complains that EmptyView and RollView are mismatched. What I want is for the system to not care, as long as it's a View. Changing the some to any brings up the error that the struct doesn't conform to View.
If I change the ternary to an if, this works, of course, but also takes up far more space.
"This happens because any View doesn't implement the View protocol."
Yes, but why not?
See, in my mind, a protocol specifies an interface, and so it ought to simple be that if the struct has the members expected of a View, the fact that it has other members as well ought not matter.
I feel like it's a weird limitation. Imagine if you made a reservation at a restaurant, and they asked what style of car you would be driving. They don't care what it is, be it sedan, hatchback, pickup, they just want to know. And they need to know before you start driving. But as far as you can tell, the parking spots are all the same, all accessible, all the time.
Consider a protocol P that requires a static property foo without a default implementation, meaning that users can get the value of this property for any conforming type without having an instance of that type. The existential type any P can't automatically pull an implementation of this static property out of thin air, so naturally it doesn't automatically conform to P .
In the future, Swift could consider allowing users to manually declare conformance of any P to P in an extension that manually supplies implementations of any such requirements. There hasn't been anyone working on this direction to my knowledge and it isn't possible to extend an existential type at all.
Even if that were to change, there will always be protocols with semantic requirements that are impossible even manually to conform. For instance, in the example above, if P is Animal and the static requirement foo is species , it is evident that any Animal cannot implement such a requirement: an existential box for an animal without an underlying animal type has no species. This shows that the existential type any Animal simply does not conform to Animal .
This is not a contrived example: substitute FixedWidthInteger and the static property bitWidth and we have an actual scenario from the standard library:
Any conforming type obviously must be able to implement the static property bitWidth without reference to any instance—that's fundamental to the semantics of the protocol. But any FixedWidthInteger is a box that can hold any fixed-width integer value: the same instance of this existential type can hold a UInt8 value today and a UInt16 value tomorrow. This demonstrates that any FixedWidthInteger is not a fixed-width integer type and therefore cannot conform to FixedWidthInteger.
Any conforming type obviously must be able to implement the static property bitWidth without reference to any instance—that's fundamental to the semantics of the protocol. But any FixedWidthInteger is a box that can hold any fixed-width integer value: the same instance of this existential type can hold a UInt8 value today and a UInt16 value tomorrow. This demonstrates that any FixedWidthInteger is not a fixed-width integer type and therefore cannot conform to FixedWidthInteger.
I find this odd, because I don't read 'any FixedWidthInteger' as a type, I read it a constraint.
Xiaodi Wu has that copypasta ready to go because people encounter your problem 523 times per minute, across Earth. (That's just the related internet post count from non-AIs; actual usage is estimated to be 700 times higher.)
Keep in mind that there’s a distinction between P (type constraint), some P (specific but unnamed type that obeys that constraint), and any P (existential type). I have a feeling the confusion isn’t helped by the fact that Swift allows the last one to be spelled P in some situations.
That's exactly what those things mean. The key insight is that your definition of some P cannot hold an any P, because any P does not conform to P, for the reasons @xwu outlined above.
The other thing that doesn't help is that even though your code looks like it returns different types on each branch, the property in fact always returns a single type due to the result-builder transform.
There's no need to get existentials involved in this. I think that's a bit of a distraction - you were only drawn to try them because of the lack of support for ternary expressions in result builders. I can't imagine why ternaries would be singled out for exclusion (SE-0289 doesn't mention them at all), so this is probably an oversight and worth filing a bug report about.
Your issues here emphasise why they should be supported. You encountered an error and I think your attempt to solve it was logical and what I would expect most Swift developers to try, given our advice about what some and any types are for. But this is really all about a deficiency in the result builder transform, so the errors sent you down entirely the wrong path.
You seem to be using "some P" and "any P" like concrete types. I see them as constraints. I read "some P" as "I'm going to hand you an instance which has the P interface", and "any P" is "I don't care what the type is, as long as it has the P interface."
In no way do I think there is some intermediate representation which you can interact with. Maybe I'm wrong? I regard them as something which gets taken care of in the compilation.
In fact, what I thought it was doing was letting me write more concise code. Let me give you my first example:
This is code I current use:
import SwiftUI
struct HVStack: View {
var vertical = true
@ViewBuilder var view: any View
var body: some View {
if vertical {
VStack {
AnyView(view)
}
} else {
HStack {
AnyView(view)
}
}
}
}
struct HVStack_Previews: PreviewProvider {
static var previews: some View {
HVStack(vertical: true) {
Text("Hi There")
Text("Jelly")
}
}
}
What I was hoping was that the "any View" meant I didn't have to wrap view in AnyView. It seems redundant.
(Even better would be if I could say let Stack = vertical ? VStack : HStack; return Stack(view). One day?)
To the compiler, they are types. We refer to any P as an “existential” type, but that’s just terminology; it’s still a type just as much as HStack is.
I’ve written my own extension method on View to facilitate this:
var body: some View {
view
.apply {
if vertical {
VStack($0)
} else {
HStack($0)
}
}
}
Still more verbose than if the ternary were supported in that way, but far better than repeating the contents in many cases.