Reputation: 513
Here, layEgg() wants to reuse chickenFactory(Chicken.Type). The problem is that layEgg returns Self? so that I get an instance of whatever type this is or nil.
However, chickenFactory returns a Chicken, which needs to be cast to whatever type I am.
enum BiologicalGender : String {
case male = "male"
case female = "female"
}
class Chicken {
let gender : BiologicalGender
required init (_ gender : BiologicalGender) {
self.gender = gender
}
class func chickenFactory(_ chickenType : Chicken.Type) -> Chicken {
if (arc4random_uniform(2) == 0) {
return chickenType.init(.male)
} else {
return chickenType.init(.female)
}
}
func layEgg() -> Self? {
if (self.gender == .female) {
return Chicken.chickenFactory(type(of:self)) // need a cast here
} else {
return nil
}
}
}
How do I cast polymorphically?
Upvotes: 3
Views: 694
Reputation: 513
This does not necessarily improve the code, but here is a class function wrapping the original factory that accomplishes the cast to Self that so many of you demonstrated.
The original, improved generic chickenFactory() is also included.
class Chicken {
let gender : BiologicalGender
required init (_ gender : BiologicalGender) {
self.gender = gender
}
class func chickenFactory<T:Chicken>(_ chickenType : T.Type) -> T {
if (arc4random_uniform(2) == 0) {
return chickenType.init(.male)
} else {
return chickenType.init(.female)
}
}
class func poorlyTypedChickenFactory(_ chickenType : Chicken.Type) -> Chicken {
if (arc4random_uniform(2) == 0) {
return chickenType.init(.male)
} else {
return chickenType.init(.female)
}
}
private class func chickenFromPoorlyTypedFactory<T>(_ chickenType : Chicken.Type) -> T?
{
let chicken = poorlyTypedChickenFactory(chickenType) as? T
return chicken
}
func layEgg() -> Self? {
if (self.gender == .female) {
return Chicken.chickenFromPoorlyTypedFactory(type(of:self))
} else {
return nil
}
}
}
Upvotes: 0
Reputation: 80781
The problem is that chickenFactory(_:)
says that it takes a type of type Chicken.Type
as an argument and outputs an instance of Chicken
. The instance that is outputted need not be of type the type you passed in (for example, it could output an instance of Chicken
when you input a type of ChickenSubclass.self
).
What you want is not type-casting, but rather a way to say that the instance outputted is the same type as the type you input. To express this, you can use a generic placeholder:
class func chickenFactory<T : Chicken>(_ chickenType : T.Type) -> T {
if (arc4random_uniform(2) == 0) {
return chickenType.init(.male)
} else {
return chickenType.init(.female)
}
}
Now
func layEgg() -> Self? {
if (self.gender == .female) {
return Chicken.chickenFactory(type(of: self))
} else {
return nil
}
}
compiles just fine, as the compiler knows that an instance of type type(of: self)
can be returned from a function returning Self?
.
Although that all being said, the type argument to chickenFactory(_:)
is seemingly redundant, you could just use the type that it's called on instead through the use of self
at static scope:
class func chickenFactory() -> Self {
if (arc4random_uniform(2) == 0) {
return self.init(.male)
} else {
return self.init(.female)
}
}
func layEgg() -> Self? {
if (self.gender == .female) {
return type(of: self).chickenFactory()
} else {
return nil
}
}
Upvotes: 4
Reputation: 3152
Another solution for the problem would be this. Check this answer
func layEgg() -> Self? {
if (self.gender == .female) {
return makeEgg()
} else {
return nil
}
}
func makeEgg<T>()->T?{
return Chicken.chickenFactory(type(of:self)) as? T
}
Upvotes: 1