Considering an rpg game, I want to model the concept of “player”, I start writing:
data Player
= Player
{ hp :: Int
, position :: Position
}
data Position
= Position Int Int
So far so good, now I want to add “goods”, players can carry an goods.
But there are many kinds of “goods”, such as iron swords, such as hematinic, so I wrote:
data ScrapIron
= ScrapIron { name :: String, addAtk :: Int }
data Hematinic
= Hematinic { name :: String, addHp :: Int }
class Goods a
instance Goods ScrapIron
instance Goods Hematinic
I wrote a type class and made both types implement the type class, meaning “iron swords and hematinic are both goods”.
This way I can write a function where the player picks up goods:
pickup :: forall goods. Goods goods => Player -> goods -> Player
Although “goods” is generic type here, it is constrained to the “Goods” type class.
But the problem is, I can’t write:
data Player
= Player
{ hp :: Int
, position :: Position
, goods :: Goods
}
Because “Goods” is a type class and not a type.
So I write:
data Player goods
= Player
{ hp :: Int
, position :: Position
, goods :: goods
}
Although this works, the program becomes very complicated, and all occurrences of the “Player” type need to pass in a generic parameter, and also qualify it as “Goods”.
To make matters worse, in the future I will add more types and this generic parameter list will get very long.
So, what is the correct way to do it?
I think the motivation for inventing type classes is to describe an “extersion” of a concept, So this way of writing is very natural:
class Goods a
instance Goods ScrapIron
instance Goods Hematinic
Am I understanding it wrong?
If this is wrong, how are type classes commonly used in modeling?
Now I changed it to this:
data Weapon
= ScrapIron { addAtk :: Int }
data Agentia
= Hematinic { addHp :: Int }
data Goods
= Weapon_gen Weapon | Agentia_gen Agentia
data Player
= Player
{ hp :: Int
, position :: Position
, goods :: Goods
}
class Pickup a where
pickup :: Player -> a -> Player
instance Pickup Agentia where
pickup (Player { hp, position }) goods = Player { hp, position, goods: Agentia_gen goods }
instance Pickup Weapon where
pickup (Player { hp, position }) goods = Player { hp, position, goods: Weapon_gen goods }
which looks good, but is it against some “best practice” or something?
Is there any better way?
Thank for your answer.