Лично я думаю, что Хаммар находится на правильном пути, указывая на сходство между Player
и Monster
.Я согласен, что вы не хотите делать их такими же , но учтите следующее: возьмите класс типов, который у вас здесь ...
class Targetable a where
name :: a -> String
level :: a -> Int
hp :: a -> HitPoints
mp :: a -> ManaPoints
status :: a -> Maybe [Status]
... и замените его натип данных:
data Targetable = Targetable { name :: String
, level :: Int
, hp :: HitPoints
, mp :: ManaPoints
, status :: Maybe [Status]
} deriving (Eq, Read, Show)
Затем выделите общие поля из Player
и Monster
:
data Monster = Monster { monsterTarget :: Targetable
, monsterElemType :: Maybe Element,
} deriving (Eq, Read, Show)
data Player = Player { playerTarget :: Targetable } deriving (Eq, Read, Show)
В зависимости от того, что вы делаете с ними, может иметь смыслвместо этого выверните его наизнанку:
data Targetable a = Targetable { target :: a
, name :: String
-- &c...
}
... и затем получите Targetable Player
и Targetable Monster
.Преимущество здесь состоит в том, что любые функции, которые работают с любой из них, могут принимать вещи типа Targetable a
- точно так же, как функции, которые приняли бы любой экземпляр класса Targetable
.
Мало того, что этот подход почти идентиченк тому, что у вас уже есть, это также код на много меньше, и он упрощает типы (не имея ограничений на классы везде).На самом деле, тип Targetable
, описанный выше, - это примерно то, что GHC создает за сценой для класса типов.
Самый большой недостаток в этом подходе заключается в том, что он делает доступ к полям более затруднительным -Кстати, некоторые вещи оказываются в два уровня глубиной, и распространение этого подхода на более сложные типы может еще глубже их вложить.Многое из того, что делает это неловким, заключается в том, что средства доступа к полям не являются «первоклассными» в языке - вы не можете передавать их как функции, абстрагироваться над ними или что-то в этом роде.Наиболее популярным решением является использование «линз», о которых уже упоминался другой ответ.Обычно я использовал для этого пакет fclabels
, так что это моя рекомендация.
Факторизованные типы, которые я предлагаю, в сочетании со стратегическим использованием линз, должны дать вам то, чтопроще в использовании, чем подход с использованием классов типов, и не загрязняет пространство имен, как это делает множество типов записей.