Понимание того, как создавать GHC.Generics Rep и преобразовывать обратно в значения

Я пытаюсь узнать, как использовать GHC.Generics< /а>. Интересная тема, но пугающая.

Читая запись в блоге 24 Days of GHC Extensions: DeriveGeneric, я научился принимать значение и перемещаться по его Rep. Хорошо.

Однако, читая запись в блоге Создание конструкторов данных с помощью GHC Generics, который описывает аналог создания Rep и преобразования его обратно в значение, я зашел в тупик. Я прочитал a номер из other resources, но без особой помощи.

В записи блога есть следующий код. Во-первых, построение 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 по пути.


person Ana    schedule 15.02.2016    source источник
comment
Обратите внимание, что здесь есть две отдельные цели проектирования (так сказать) - в общем случае получить функцию и сделать так, чтобы эта функция была приятной. Автор хотел написать многовариантную функцию, такую ​​как a -> b -> ... -> R, вместо цепочки Compose, которую генерирует класс Mk: Compose ((->) a) (Compose ((->) b) ... R. Если вы пытаетесь понять дженерики, я думаю, вы можете игнорировать все, кроме класса Mk, который здесь является рабочей лошадкой для дженериков. Я не знаю, почему автор решил написать все с помощью Compose, а затем преобразовать его в поливариативную функцию.   -  person user2407038    schedule 16.02.2016


Ответы (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 оборачивает самое внутреннее значение из них обоих.

person zakyggaps    schedule 16.02.2016