gabrielmaldi
gabrielmaldi

Reputation: 2196

Haskell - Calling a function defined in a typeclass

Given a typeclass:

class AnimalTrainer animal food where
    getFood :: animal -> (food, Int) -- Returns the food and the quantity
    feed :: animal -> (food, Int) -- Returns the leftovers

    feed a = feed' (getFood a) -- Provide a default implementation
        where feed' (f, n) = (f, n - 1)

And an instance:

data Animal = Dog | Cat
data Food = Meat | Milk

instance AnimalTrainer Animal Food where
    getFood Dog = (Meat, 2)
    getFood Cat = (Milk, 3)

How can I write another function (somewhere else) that calls the feed function defined in the typeclass? Example:

feedEverything :: Bool
feedEverything = snd (feed Dog) == 0

Thanks

Upvotes: 0

Views: 269

Answers (1)

Dietrich Epp
Dietrich Epp

Reputation: 213338

The problem is that Haskell can't figure out what type you want to use for food. It sees one instance:

instance AnimalTrainer Animal Food

But maybe there is a second instance somewhere...

instance AnimalTrainer Animal Poison

So you need to tell Haskell that animals get food only, and not something else like poison.

Solution 1: You can use functional dependencies:

class AnimalTrainer animal food | animal -> food where
    ...

This tells Haskell that for each animal type, there is only one food type that it will eat.

Solution 2: You can also use type families.

class AnimalTrainer animal where
    type AnimalFood animal :: *
    getFood :: animal -> (AnimalFood animal, Int)
    feed :: animal -> (AnimalFood animal, Int)

    feed a = feed' (getFood a) -- Provide a default implementation
        where feed' (f, n) = (f, n - 1)

data Animal = Dog | Cat
data Food = Meat | Milk

instance AnimalTrainer Animal where
    type AnimalFood Animal = Food
    getFood Dog = (Meat, 2)
    getFood Cat = (Milk, 3)

I personally consider this solution a little more esoteric, and the syntax a little less natural. But the example is contrived, so I include this for completeness.

Solution 3: You could add explicit type annotations whenever you call feed.

feedEverything = snd ((feed :: Animal -> (Food, Int)) Dog) == 0

Upvotes: 10

Related Questions