s4y
s4y

Reputation: 51715

Swift: call non-generic function from generic?

I have many hungry FoodMonsters who can munch many different types of food. The monsters have separate, overloaded methods to munch each type.

I'd like to build a MonsterPack which manages many monsters and can feed them all any type of food a FoodMonster can munch. I’ve implemented it like this:

protocol Cookie {}
protocol Lettuce {}

protocol FoodMonster {
    func munch(Cookie)
    func munch(Lettuce)
}

struct MonsterPack {

    var monsters = [FoodMonster]()

    func feed<T>(food: T) {
        for monster in monsters {
            monster.munch(food)
        }
    }
}

Unfortunately, this implementation of MonsterPack.feed doesn’t compile, since it’s generic and FoodMonsters cannot, as much as they might want to, munch any type of value.

Is there any way I can define feed such that I can call it with any value that a FoodMonster can munch?

Upvotes: 2

Views: 134

Answers (2)

Rob Napier
Rob Napier

Reputation: 299455

GoZoner's solution is appealing, but is actually just subclassing. In fact, it's identical to this (notice the lack of T):

protocol Food {}
protocol Lettuce : Food {}
protocol Cookie  : Food {}

protocol FoodMonster {
    func munch(food: Food)
}

struct MonsterPack {
    var monsters = [FoodMonster]()

    func feed(food: Food) {
        for monster in monsters {
            monster.munch(food)
        }
    }
}

There's no need for generics at all. Type parameterization doesn't really make sense in this case, since you're just relying on the protocol. There's no reason to say "T, where T is a Food." That's just "Food." (This kind of over-parameterization is really common, and you should be constantly watching for it. It's easy to use generics where you just meant "conforms to.")

If you can use a protocol such that Lettuce and Cookie both have some method in common, that's awesome. But that's not possible in the given example. There is no method at all I can call on a Cookie. And I take from your question is that there are really a specific set of foods out there, and a monster must be able to eat all of them in specific ways. If that's the case, then any real monster is going to wind up with type-checking in munch. That's not subclassing. That's a sum-type, which in Swift is called an enum.

enum Food {
    case Cookie
    case Lettuce
}

protocol FoodMonster {
    func munch(Food)
}

struct MonsterPack {
    var monsters = [FoodMonster]()

    func feed(food: Food) {
        for monster in monsters {
            monster.munch(food)
        }
    }
}

struct RealMonster {
    func feed(food: Food) {
        switch food {
        case .Cookie: break // Do the cookie thing
        case .Lettuce: break // Do the lettuce thing
        }
    }
}

Now what I'm saying is "if you want to be a monster, you're going to have to be able to munch all of these things which may have no protocol in common." If that's what you mean, then enum is your tool, not subclasses, protocols, or generics.

Upvotes: 2

GoZoner
GoZoner

Reputation: 70185

Start by capturing the commonality between Lettuce and Cookie as Food.

protocol Food {}
protocol Lettuce : Food {}
protocol Cookie  : Food {}

then specify that you feed monsters food:

func feed<T:Food>(food: T) {
    for monster in monsters {
        monster.munch(food)
    }
}

and make munch itself generic as:

protocol FoodMonster {
  func munch<T:Food> (food:T)
}

Upvotes: 2

Related Questions