Возможно, что удивительно, это связано с тем, что Glass
является поликодовым:
*Main> :kind! Glass
Glass :: k -> *
Между тем, в отличие от параметра типа Glass
, «запись» в HasField
должна быть вида Type
:
*Main> :set -XPolyKinds
*Main> import GHC.Records
*Main GHC.Records> :kind HasField
HasField :: k -> * -> * -> Constraint
Если я добавлю отдельную добрую подпись, например, такую:
{-# LANGUAGE StandaloneKindSignatures #-}
import Data.Kind (Type)
type Glass :: Type -> Type
data Glass r = Glass
, то она будет проверяться даже при (HasField k r v, x ~ r)
.
На самом деле, с доброй подписью «трюк ограничения» перестает быть необходимым:
instance HasField k r v => HasField k (Glass r) (ReifiedGetter r v) where
getField _ = Getter (to (getField @k))
main :: IO ()
main = do
print $ Person "foo" 0 ^. runGetter (getField @"name" the)
print $ Person "foo" 0 ^. runGetter (getField @"age" the)
Здесь поток информации при проверке типов выглядит так:
- Мы знаем, что у нас есть
Person
, поэтому - через runGetter
- тип поля в HasField
должен быть ReifiedGetter Person v
, а r
должен быть Person
. - , поскольку
r
Person
, тип источника в HasField
должен быть Glass Person
. Теперь мы можем разрешить тривиальный экземпляр Glassy
для the
. - Ключ
k
в HasField
задается как литерал типа: Symbol
name
. - Мы проверяем предварительные условия экземпляра. Мы знаем
k
и r
, и они совместно определяют v
из-за функциональной зависимости HasField
. Экземпляр существует (автоматически генерируется для типов записей), и теперь мы знаем, что v
равно String
. Мы успешно удалили все типы.