At m
обеспечивает at :: Index m -> Lens' m (Maybe (IxValue m))
.Обратите внимание, что Lens' m _
означает, что m
- это конкретный тип, такой как Int
или Map ImageId Sprite
, а не конструктор типа, такой как Map
.Если вы хотите сказать, что m ImageId Sprite
«похож на карту», вам понадобятся следующие 3 ограничения:
At (m ImageId Sprite)
: предоставляет at
для индексации и обновления. Index (m ImageId Sprite) ~ ImageId
: ключи, используемые для индексации m ImageId Sprite
с: ImageId
с. IxValue (m ImageId Sprite) ~ Sprite
: значения в m ImageId Sprite
: Sprite
с.
Вы можете попытаться поместить это ограничение в Game
(хотя это все еще неправильно):
data Game m e = Game {
initial :: e,
-- ...
sprites :: (At (m ImageId Sprite),
Index (m ImageId Sprite) ~ ImageId,
IxValue (m ImageId Sprite) ~ Sprite) =>
IO (m ImageId Sprite)
}
Обратите внимание, что я говорю m ImageId Sprite
несколько раз, но я не применяю m
к другим (или меньшим) параметрам.Это подсказка, что вам не нужно абстрагироваться от m :: * -> * -> *
(такие вещи, как Map
).Вам нужно только абстрагироваться более m :: *
.
-- still wrong, though
type IsSpriteMap m = (At m, Index m ~ ImageId, IxValue m ~ Sprite)
data Game m e = Game {
initial :: e,
-- ...
sprites :: IsSpriteMap m => IO m
}
Это хорошо: если вы когда-нибудь создадите специализированную карту для этой структуры данных, например
data SpriteMap
instance At SpriteMap
type instance Index SpriteMap = ImageId
type instance IxValue SpriteMap = IxValue
, вы не сможетеиспользовать его с слишком абстрактным Game
, но он вписывается прямо в менее абстрактный как Game SpriteMap e
.
Это все же неправильно, потому что ограничение находится в неправильном месте.Что вы сделали здесь, так это скажите: если у вас есть Game m e
, вы можете получить m
, если вы докажете, что m
mappish.Если я хочу создать a Game m e
, я не обязан доказывать, что m
вообще маппский.Если вы не понимаете почему, представьте, можете ли вы заменить =>
на ->
выше.Человек, который звонит sprites
, передает доказательство того, что m
похож на карту, но Game
не содержит само доказательство.
Если вы хотитеоставив m
в качестве параметра Game
, вы должны просто написать:
data Game m e = Game {
initial :: e,
-- ...
sprites :: IO m
}
и написать каждую функцию, которая должна использовать m
в качестве карты, например:
doSomething :: IsSpriteMap m => Game m e -> IO ()
Или вы можете использовать экзистенциальную квантификацию:
data Game e = forall m. IsSpriteMap m => Game {
initial :: e,
-- ...
sprites :: IO m
}
Чтобы построить Game e
, вы можете использовать что-нибудь типа IO m
для заполнения sprites
, пока IsSpriteMap m
.Когда вы используете Game e
в сопоставлении с шаблоном, сопоставление с шаблоном свяжет (неназванную) переменную типа (назовем это m
), а затем даст вам IO m
и подтверждение для IsSpriteMap m
.
doSomething :: Game e -> IO ()
doSomething Game{..} = do sprites' <- sprites
imageId <- _
let sprite = sprites'^.at imageId
_
Вы также можете сохранить m
в качестве параметра Game
, но при этом сохранить контекст в конструкторе Game
.Тем не менее, я призываю вас просто пойти с первым вариантом установки контекста на каждую функцию, если у вас нет причин не делать этого.
(Весь код в этом ответе выдает ошибки о расширении языка.{-# LANGUAGE <exts> #-}
прагма в верхней части вашего файла, пока GHC не успокоится.)