Понимание того, как построить 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 возвращает функтор Мои вопросы:

  1. Что такое mk возвращение? Почему это функтор? Какова интерпретация его результата? Я вижу, что K1 i c экземпляр Mk возвращает функцию (я понимаю, что это функтор), которая принимает значение и переносит его в K1, но mk за Mk (l :*: r) а также Mk (M1 i c f) полностью потеряны на мне.

  2. я догадываюсь Compose происходит от Data.Functor.Compose, что означает, что когда я делаю fmap f x это делает fmap два уровня глубоко в составленные функторы. Но я не могу понять смысл вложенного fmap с внутри Compose,

  3. Для примера M1 i c f Я думал, что это просто обернуть внутренние ценности в M1 так надо M1 <$> mk или же fmap M1 mk не имеет смысла для меня.

Очевидно, я не понимаю намерения или значения этих примеров и того, как эти экземпляры взаимодействуют, чтобы создать окончательный вариант. Rep, Я надеюсь, что кто-то может просветить меня и дать хорошее объяснение того, как использовать GHC.Generics по пути.

1 ответ

Решение
  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?

И скоро поймете, что это невозможно из-за нескольких проблем:

  1. ->тип функции в haskell принимает только один аргумент за раз, поэтому mk не сможет вернуть ничего разумного, если конструктор принимает более одного аргумента.
  2. Количество и тип аргументов неясны: это относительно rep введите беспокойство.
  3. Это не может быть просто 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,

  1. я догадываюсь 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')
  1. Для примера M1 i c fЯ думал, что это просто обернуть внутренние ценности в M1так надо M1 <$> mk или же fmap M1 mk не имеет смысла для меня.

Это просто обернуть внутренние значения в M1, но неясно, как долго список аргументов в основе f является. Если это займет один аргумент, то mk это функция, в противном случае это Compose. fmap оборачивает самую внутреннюю ценность их обоих.

Другие вопросы по тегам