I have a generic struct called Race, which takes in S as the generic parameter
Depending on the type of S I want to be able to have a different implementation of isAvailable
I have made my attempt, by switching over the type of status
Question
Just wondering if there is a better way to implement isAvailable using generic specialization or other means or is my code the best possible way?
Code
protocol Raceable {
associatedtype S: Status
var status: S { get }
var isAvailable: Bool { get }
}
struct Race<S: Status>: Raceable {
let status: S
// Is there a better way to implement this?
var isAvailable: Bool {
switch status {
case is StatusA:
false
case is StatusB:
true
default:
false
}
}
}
protocol Status: Codable, Equatable {}
enum StatusA: Status {
case yetToStart
case inProgress
case completed
}
enum StatusB: Status {
case idle
case inProgress
case aborted
case completed
}
Thanks @Danny but I had tried it but it is not what I intended.
The problem with that approach is if you use via protocol you will get the default implementation instead of the specialization (it may not be a bug but it is not what I intended).
Example (from your code)
let p1: any Raceable = Race(status: StatusB.idle)
print("p1.isAvailable: \(p1.isAvailable)")
You could add an isAvailable requirement to Raceable, which would then allow you to dynamically dispatch to the implementation of the generic type at runtime. Here I think you’d could make it static since it only depends on the type and not the value.
Thanks @j-f1, that is what I thought too, but may be I am not understanding it fully.
In my example isAvailable is already part of the requirement and yet default implementation is taken. Could you try with the sample code I am getting the default implementation of false when accessed through a protocol.
I couldn't use static because in the real world problem I still would need to examine the instance's status value to determine value isAvailable, this example was a simplified version of the real world problem.
Your solution to switch over the type is one way of doing this.
You could alternatively make a property named something like isRaceAvailable a (if you want, static) requirement of Status and call that from a (if you want, default) implementation of Race.isAvailable.
As soon as you erase the Status, you lose your specializations. I recommend figuring out how to erase the need to use any Raceable. You surely won't be able to get it as simple as this, but you may have some option of otherwise passing the Status through to functions and types.
protocol Raceable<S> {
associatedtype S: Status
var status: S { get }
}
struct Race<S: Status>: Raceable {
let status: S
}
extension Raceable<StatusA> {
var isAvailable: Bool { false }
}
extension Raceable<StatusB> {
var isAvailable: Bool { true }
}
let p1: some Raceable<StatusB> = Race(status: StatusB.completed)
#expect(p1.isAvailable)
Thanks a lot @xwu, I think adding it as a static requirement to Status is a nice alternative.
One more question I remember another post where you explained what was happening, I couldn't seem to get the link.
Just want to confirm my understanding:
When accessing through a protocol, the protocol's default implementation is used because the implementation to use is determined at compile time (static dispatch) .. and not dynamic dispatch?
No, protocol requirements are dynamically dispatched. The protocol’s default implementation is used because it is the only implementation that witnesses the requirement. A type only conforms to a protocol in exactly one way, and you have not given Race a non-default implementation.
Thanks a lot @xwu, I am slowing understanding ... please bear with me
protocol requirements are dynamically dispatched, however A type only conforms to a protocol in exactly one way
I suppose only because of protocol using dynamic dispatch polymorphism works we get the inherited class's implementation invoked (not referring to my example, but inheritance in general)
The part I completely missed is A type only conforms to a protocol in exactly one way. So to the compiler that is the rule, so no 2 options.
Just curious wouldn't this detail trip a lot of developers or are there scenarios where such behavior is actually useful (specializations used when using concrete types and protocol implementation used when using protocols.)