class Parent {
var someVar: SomeType? {
get { ... }
}
func someFunc() -> SomeType? {
...
}
}
class Child: Parent {
// no compiler error
var someVar: SomeType {
get { ... }
}
// no compiler error
func someFunc() -> SomeType {
...
}
}
...but this is not ok?
protocol Proto {
var someVar: SomeType? { get }
}
struct SomeStruct: Proto {
let someVar: SomeType
}
/// error: Type 'SomeStruct' does not conform to protocol 'Proto'
/// note: Candidate has non-matching type 'SomeType'
Because SomeStruct does not provide someVar that is an optional type. Your classes Parent and Child do not conform to any protocols, so you are dealing with traditional inheritance and overloading. SomeStruct does not inherit anything, it only has to conform to a protocol, which in your case, it does not, hence the error message.
Your Child class actually has two overloaded computed variables, and two overloaded functions. Overloads are permitted that only differ in the return type.
The answer isn‘t that simple actually, it‘s not just a strict misalignment of protocol requirements here, because theoretically T is a subtype of T? and the protocol of the original post should be satisfied because it‘s safe to promote a non-optional value as an optional one.
Someone has to tackle the issue with a formal proposal still.
@DevAndArtist that's very interesting! And this "trick" works even today, five years later.
@jonprescott yes but the protocol property is a get-only one, so I expect a non-optional implementation to work just fine! Because it seems that a similar rule is already in place for initialisers (look at DevAndArtist's link).
I would reply to both you and @DevAndArtist that, although you may be right, theoretically, in my experience, the compiler does not work that way. That may be a limitation of the implementation that needs to be fixed, but, at least in my experience, that is the way the compiler often works.
Well sure, right now it does not work because T and T? are strictly speaking different types but with some compiler magic and generalization of the theoretical subtype relationship we could potentially allow this behavior in Swift. (I can‘t wait for it myself.)
Especially because we recently got this paragraph merged into the the manifesto:
My response to @technicated is how the compiler works today. As you've noted, as the generics manifesto is implemented over time, this relationship may be made more robust in the future. As far I can see, it does not work that way now.
I think your issue is with the last invocation. I think you might be seeing the effects of nil coalescence. bar2(value::optionalString) becomes in some way, bar2(value: (String?)?) if Value == String?. The nils get coalesced into a single nil, then the pattern matching is done, if I understand the algorithms.
bar1(value: string as String) // Value == String
bar2(value: string as String?) // Value == String
bar1(value: optionalString as String?) // Value == String?
bar2(value: optionalString as String?) // Value == String
One thing to note is your examples aren't quite equal. Your Class property is computed. Your struct property is not. This also isn't allowed in a class when your property has storage, (though still not allowed if your struct property is computed).
class Parent {
var someVar: String? = "hi"
}
class Child: Parent {
override var someVar: String = "hi"
// ERROR: Cannot override mutable property 'someVar' of type 'String?' with covariant type 'String'
}
It appears this is only allowed when there's no actual storage of the property.
Since a protocol definition can't know whether the adhering property does or does not involve storage, it has to assume it can, which apparently isn't allowed.
In my testing a child class can override an optional computed property with a non-optional version:
class Parent {
var someVar: String? {"hi"}
}
class Child: Parent {
override var someVar: String {"hiChild" }
}
let optionalVar: String? = Child().someVar
print(optionalVar) // prints "hiChild"
This is kind of weird behavior and almost feels like a bug. My naive-purely speculative guess is the cause lies in the difference in how computed properties and stored properties are dispatched. But that's delving into a topic I don't know well enough to really comment on
The problem in your example is not the fact that the property has storage, but the fact that it is both gettable and settable. So a subtype cannot override it with a covariant type because of the setter, which cannot accept a more specific type. If it would, and I use Child cast as a Parent, I could assign nil to a non-optional someVar, and this is not valid.
But the protocol only wants the property to be gettable, so a non optional conformance should be fine... but I guess this is consistent with the following:
protocol Protocol {
var v: Parent { get }
}
struct MyStruct: Proto {
var v: Child
}
// `MyStruct` does not conform to `Proto`, if I wanted to do this I should have used
// an associatedtype as the type of `v`
class Parent {
let someVar: String? = "hi"
}
class Child: Parent {
override let someVar: String = "hiChild"
// Error: cannot override with a stored property 'someVar'
}
So there's no way to have storage, have only get, and be able to override.
No idea if that's a formal "rule" or just odd emergent behavior. Either way it's not exactly intuitive
The latter is allowed, but only if you are using an associatedtype, and this is why my original code does not work. This has nothing to do with storage vs get-set.
In my original post, I expected Optional covariance to kick in to let me fulfil the protocol requirement, but that's simply not how the compiler works today - maybe in future it will.
DevAndArtist's "hack" (creating a default implementation) instead works, because in the concrete / implementor type you are overloading the default implementation and this the compiler will allow!
??? Your OP is asking why it's not allowed. I'm saying it seems to have something to do with storage.
Right. This is different behavior from overriding the property in your subclass. This results in 2 separate properties, one with Type? one with with Type.
protocol Proto {
var someVar: String? { get }
}
extension Proto {
var someVar: String? { "Extension" }
}
//
struct SomeStruct: Proto {
var someVar: String { "Implementor" }
}
let thing: String = SomeStruct().someVar
print(thing) // prints "Implementor"
let optionalThing: String? = SomeStruct().someVar
print(optionalThing) // prints "Optional(Extension)"
But I'm saying that this should have nothing to do with storage, but with covariance...
Yeah, in my OP I was asking why, and my understanding now is that even if T is considered a subtype of Optional<T> (although a "special" kind of subtype), this rule is not considered while evaluating protocol conformances. If you want to use covariance in you protocols implementations, you should use an associatedtype. And this brings me to what I meant by "the latter is allowed":
protocol Proto {
associatedtype T: Parent
var value: T { get }
}
class Parent {}
class Child: Parent {}
struct Implementor: Proto {
// This is good, I can use a subtype of `Parent` to fulfil the protocol requirement
let value: Child
}
The "problem" with this approach - for my specific problem - is that we cannot express the subtype relationship of T and Optional<T> in the type system because that is automagically handled by the compiler as of today.
Yeah, that seems to describe the difference better than my attempt
FWIW, I prefer to avoid associatedtype unless absolutely necessary.
protocol Proto {
var someVar: String? { get }
}
struct SomeStruct: Proto {
var someVar: String? { realVar }
let realVar: String
}
This is admittedly a rabbit trail, but I'm curious if my way of thinking makes sense.
"subtype" doesn't seem like quite the right abstraction for reasoning about this. That's not the way it's expressed at the Type level since Optional<T>wrapsT. My understanding is that generally subtype refers to a parent->child relationship where the child has a superset of the parent's attributes. e.g. all Ducks are Birds, but not all Birds are Ducks. That doesn't describe the relationship between Optional<T> and T.
Optional<T> has all of, or none ofT's attributes, and Tnever has all of Optional<T>'s attributes (since it can never be Optional<T>.none). There is a lot of functionality to easily switch between the two so it often behaves similar to a subtype, but the traditional type->subtype abstraction, (especially from OOP), breaks down for Optional once you get into corner cases like the one in the OP.
It's less of a parent->child relationship, and more of an aunt/uncle->niece/nephew relationship. Maybe there's a name for that? I'm not familiar enough with Type theory to know