Понимание того, как построить GHC.Generics Rep и преобразовать обратно в значения
Я пытаюсь узнать о том, как использовать GHC.Generics
, Увлекательная тема, но пугающая.
Читая запись в блоге " 24 дня расширений GHC: DeriveGeneric", я узнал, как получить значение и перемещаться по нему. Rep
, Хорошо.
Тем не менее, читая запись в блоге Построение конструкторов данных с помощью GHC Generics, которая описывает аналог построения Rep
и преобразовав его обратно в значение, я оступился. Я прочитал ряд других ресурсов, но без большой помощи.
В записи блога есть следующий код. Во-первых, построение Rep
:
class Functor f => Mk rep f | rep -> f where
mk :: f (rep a)
instance Mk (K1 i c) ((->) c) where
mk = \x -> K1 x
instance (Mk l fl, Mk r fr) => Mk (l :*: r) (Compose fl fr) where
mk = Compose (fmap (\l -> fmap (\r -> l :*: r) mk) mk)
instance (Mk f f') => Mk (M1 i c f) f' where
mk = M1 <$> mk
Затем, имея дело с Compose
:
class Functor f => Apply f a b | f a -> b where
apply :: f a -> b
instance Apply ((->) a) b (a -> b) where
apply = id
instance (Apply g a b, Apply f b c) => Apply (Compose f g) a c where
apply (Compose x) = apply (fmap apply x)
Тогда имеем дело с неопределенностью типа:
type family Returns (f :: *) :: * where
Returns (a -> b) = Returns b
Returns r = r
make :: forall b f z. (Generic (Returns b), Apply f (Returns b) b, Mk (Rep (Returns b)) f) => b
make = apply (fmap (to :: Rep (Returns b) z -> (Returns b)) (mk :: f (Rep (Returns b) z)))
Вот это да.
На самом деле, я застрял в самом начале, в классе Mk
где mk
возвращает функтор Мои вопросы:
Что такое
mk
возвращение? Почему это функтор? Какова интерпретация его результата? Я вижу, чтоK1 i c
экземплярMk
возвращает функцию (я понимаю, что это функтор), которая принимает значение и переносит его вK1
, ноmk
заMk (l :*: r)
а такжеMk (M1 i c f)
полностью потеряны на мне.я догадываюсь
Compose
происходит отData.Functor.Compose
, что означает, что когда я делаюfmap f x
это делаетfmap
два уровня глубоко в составленные функторы. Но я не могу понять смысл вложенногоfmap
с внутриCompose
,Для примера
M1 i c f
Я думал, что это просто обернуть внутренние ценности вM1
так надоM1 <$> mk
или жеfmap M1 mk
не имеет смысла для меня.
Очевидно, я не понимаю намерения или значения этих примеров и того, как эти экземпляры взаимодействуют, чтобы создать окончательный вариант. Rep
, Я надеюсь, что кто-то может просветить меня и дать хорошее объяснение того, как использовать GHC.Generics
по пути.
1 ответ
- Что такое
mk
возвращение?
Давайте сначала рассмотрим гораздо более простой пример В документации GHC.Generics. Для достижения общей функции encode :: Generic a => a -> [Bool]
этот бит сериализует каждый тип данных, который имеет экземпляр Generic, они определили класс типа ниже:
class Encode' rep where
encode' :: rep p -> [Bool]
Определяя Encode'
экземпляры для каждого типа Rep (M1, K1 и т. д.) заставляли функцию работать универсально для всех типов данных.
В Построении конструкторов данных с помощью GHC Generics конечной целью автора является универсальная функция make :: Generic a => TypeOfConstructor a
так наивно можно определить:
class Mk rep where
mk :: (? -> p) -- what should '?' be?
И скоро поймете, что это невозможно из-за нескольких проблем:
->
тип функции в haskell принимает только один аргумент за раз, поэтомуmk
не сможет вернуть ничего разумного, если конструктор принимает более одного аргумента.- Количество и тип аргументов неясны: это относительно
rep
введите беспокойство. - Это не может быть просто
p
как тип результата. Безrep
контекст невозможно получить экземпляры для:*:
или же:+:
и функция больше не будет работать с вложенными типами данных.
Проблема 1 может быть решена с помощью Data.Functor.Compose. Функция типа a -> b -> c
может быть закодирован в Compose ((->) a) ((->) b) c
, он может быть дополнительно составлен, в то время как хранит много информации о типах аргументов. И сделав его параметром типа Mk
, проблема 2 тоже решена:
class Functor f => Mk rep f | rep -> f where
mk :: f (rep p)
где f
обобщение закончено Compose f g
а также (->) a
, который содержит информацию на уровне типа для построения rep p
все до финала ->
в a -> b -> c -> ... -> rep p
,
- я догадываюсь
Compose
происходит отData.Functor.Compose
, что означает, что когда я делаюfmap f x
это делаетfmap
два уровня глубоко в составленные функторы. Но я не могу понять смысл вложенногоfmap
с внутриCompose
,
в Mk
экземпляр :*:
:
instance (Mk l fl, Mk r fr) => Mk (l :*: r) (Compose fl fr) where
mk = Compose (fmap (\l -> fmap (\r -> l :*: r) mk) mk)
fmap
изменяет только самый внутренний тип вложенного Compose, в этом случае изменяется конечный результат n-арной функции. mk
здесь буквально объединяются два списка аргументов fl
а также fr
, вкладывая свои результаты в вид продукта, а именно
f :: Compose ((->) a) ((->) b) (f r)
g :: Compose ((->) c) ((->) d) (g r)
mk f g :: Compose (Compose ((->) a) ((->) b)) (Compose ((->) c) ((->) d)) ((:*:) f g r)
-- or unwrapped and simplified
(a -> b -> r) -> (c -> d -> r') -> a -> b -> c -> d -> (r, r')
- Для примера
M1 i c f
Я думал, что это просто обернуть внутренние ценности вM1
так надоM1 <$> mk
или жеfmap M1 mk
не имеет смысла для меня.
Это просто обернуть внутренние значения в M1
, но неясно, как долго список аргументов в основе f
является. Если это займет один аргумент, то mk
это функция, в противном случае это Compose. fmap
оборачивает самую внутреннюю ценность их обоих.