Молния Comonads, В Общем

учитывая любой тип контейнера, мы можем сформировать молнию (сфокусированную на элементе) и знать, что эта структура является Комонадой. Это было недавно исследовано в замечательных деталях в еще один вопрос переполнения стека для следующего типа:

data Bin a = Branch (Bin a) a (Bin a) | Leaf a deriving Functor

со следующими молнии

data Dir = L | R
data Step a = Step a Dir (Bin a)   deriving Functor
data Zip  a = Zip [Step a] (Bin a) deriving Functor
instance Comonad Zip where ...

дело в том, что Zip это Comonad хотя конструкция его экземпляра немного волосатая. Что сказал:Zip можно вполне механически вывести от Tree и (я считаю) любой тип, полученный таким образом, автоматически является Comonad, поэтому я считаю, что это должно быть так, что мы можем построить эти типы и их комонады обобщенно и автоматически.

одним из методов достижения общности для конструкции молнии является использование следующего класса и семейства типов

data Zipper t a = Zipper { diff :: D t a, here :: a }

deriving instance Diff t => Functor (Zipper t)

class (Functor t, Functor (D t)) => Diff t where
  data D t :: * -> *
  inTo  :: t a -> t (Zipper t a)
  outOf :: Zipper t a -> t a

который (более или менее) появился в потоках Haskell Cafe и в блоге Конела Эллиота. Этот класс может быть создан для различных ядер алгебраические типы и, таким образом, обеспечивает общую основу для разговора о производных АДЦ.

Итак, в конечном счете, мой вопрос заключается в том, можем ли мы писать

instance Diff t => Comonad (Zipper t) where ...

который может использоваться для включения конкретного экземпляра Comonad, описанного выше:

instance Diff Bin where
  data D Bin a = DBin { context :: [Step a], descend :: Maybe (Bin a, Bin a) }
  ...

к сожалению, мне не удалось написать такой пример. Это inTo/outOf достаточное количество подписей? Нужно ли что-то еще, чтобы ограничить типы? Этот экземпляр даже возможно?

3 ответов


как ловец детей в Chitty-Chitty-Bang-Bang заманивает детей в плен со сладостями и игрушками, рекрутеры в физике бакалавриата любят дурачиться с мыльными пузырями и бумерангами, но когда дверь лязгает, это "правильно, дети, время, чтобы узнать о частичной дифференциации!". Я тоже. Не говори, что я тебя не предупреждал.

вот еще одно предупреждение: следующий код {-# LANGUAGE KitchenSink #-}, а точнее

{-# LANGUAGE TypeFamilies, FlexibleContexts, TupleSections, GADTs, DataKinds,
    TypeOperators, FlexibleInstances, RankNTypes, ScopedTypeVariables,
    StandaloneDeriving, UndecidableInstances #-}

в частности, нет порядок.

дифференцируемые функторы дают комонадические молнии

что такое дифференцируемый функтор, в любом случае?

class (Functor f, Functor (DF f)) => Diff1 f where
  type DF f :: * -> *
  upF      ::  ZF f x  ->  f x
  downF    ::  f x     ->  f (ZF f x)
  aroundF  ::  ZF f x  ->  ZF f (ZF f x)

data ZF f x = (:<-:) {cxF :: DF f x, elF :: x}

это функтор, который имеет производную, которая также является функтором. Производная представляет одно отверстие контекста для элемент. Тип молнии ZF f x представляет пару контекста с одним отверстием и элемент в отверстии.

операции Diff1 опишите виды навигации, которые мы может делать на молниях (без всяких понятий "влево" и "вправо", за которыми вижу мой клоуны и джокеры документ). Мы можем пойти "вверх", Повторно собирая структуру путем затыкать элемент в своем отверстии. Мы можем идти "вниз", находя каждый способ посетить элемент в данной структуре: мы украшаем каждый элемент своим контекстом. Мы можем пойти "вокруг", взяв существующую молнию и украсив каждый элемент своим контекстом, мы находим все способы перефокусироваться (и как сохранить наш текущий внимание.)

теперь, типа aroundF может напомнить некоторым из вас

class Functor c => Comonad c where
  extract    :: c x -> x
  duplicate  :: c x -> c (c x)

и вы правы, что напомнили! У нас есть, с прыжком и скипом,

instance Diff1 f => Functor (ZF f) where
  fmap f (df :<-: x) = fmap f df :<-: f x

instance Diff1 f => Comonad (ZF f) where
  extract    = elF
  duplicate  = aroundF

и мы настаиваем, что

extract . duplicate == id
fmap extract . duplicate == id
duplicate . duplicate == fmap duplicate . duplicate

нам это тоже нужно

fmap extract (downF xs) == xs              -- downF decorates the element in position
fmap upF (downF xs) = fmap (const xs) xs   -- downF gives the correct context

полиномиальные функторы дифференцируемы

постоянный функторы являются дифференцируемыми.

data KF a x = KF a
instance Functor (KF a) where
  fmap f (KF a) = KF a

instance Diff1 (KF a) where
  type DF (KF a) = KF Void
  upF (KF w :<-: _) = absurd w
  downF (KF a) = KF a
  aroundF (KF w :<-: _) = absurd w

некуда поставить элемент, так что невозможно сформировать контекст. Некуда идти upF или downF от, и мы легко найти все ни один из способов пойти downF.

на личность функтор является дифференцируемой.

data IF x = IF x
instance Functor IF where
  fmap f (IF x) = IF (f x)

instance Diff1 IF where
  type DF IF = KF ()
  upF (KF () :<-: x) = IF x
  downF (IF x) = IF (KF () :<-: x)
  aroundF z@(KF () :<-: x) = KF () :<-: z

в тривиальном контексте есть один элемент,downF находит его, upF перепаковывает его, и aroundF могу только оставаться на месте.

Sum сохраняет дифференцируемости.

data (f :+: g) x = LF (f x) | RF (g x)
instance (Functor f, Functor g) => Functor (f :+: g) where
  fmap h (LF f) = LF (fmap h f)
  fmap h (RF g) = RF (fmap h g)

instance (Diff1 f, Diff1 g) => Diff1 (f :+: g) where
  type DF (f :+: g) = DF f :+: DF g
  upF (LF f' :<-: x) = LF (upF (f' :<-: x))
  upF (RF g' :<-: x) = RF (upF (g' :<-: x))

другие биты и кусочки немного больше горсти. Идти downF, мы должны идти downF внутри помеченного компонента, затем исправьте полученные молнии, чтобы показать тег в контексте.

  downF (LF f) = LF (fmap (\ (f' :<-: x) -> LF f' :<-: x) (downF f))
  downF (RF g) = RF (fmap (\ (g' :<-: x) -> RF g' :<-: x) (downF g))

нужно aroundF, мы снимаем тег, выясняем, как обойти непомеченную вещь, затем восстанавливаем тег во всех результирующих молниях. Элемент в фокусе, x, заменяется всей молнией,z.

  aroundF z@(LF f' :<-: (x :: x)) =
    LF (fmap (\ (f' :<-: x) -> LF f' :<-: x) . cxF $ aroundF (f' :<-: x :: ZF f x))
    :<-: z
  aroundF z@(RF g' :<-: (x :: x)) =
    RF (fmap (\ (g' :<-: x) -> RF g' :<-: x) . cxF $ aroundF (g' :<-: x :: ZF g x))
    :<-: z

обратите внимание, что я должен использовать ScopedTypeVariables для того чтобы disambiguate рекурсивные вызовы aroundF. Как функция типа,DF не является инъективным, поэтому тот факт, что f' :: D f x недостаточно, чтобы заставить f' :<-: x :: Z f x.

продукт сохраняет дифференцируемости.

data (f :*: g) x = f x :*: g x
instance (Functor f, Functor g) => Functor (f :*: g) where
  fmap h (f :*: g) = fmap h f :*: fmap h g

чтобы сосредоточиться на элементе в паре, вы либо фокусируетесь на левом и оставляете право в покое, либо наоборот. Знаменитое правило Лейбница соответствует простой пространственной интуиции!

instance (Diff1 f, Diff1 g) => Diff1 (f :*: g) where
  type DF (f :*: g) = (DF f :*: g) :+: (f :*: DF g)
  upF (LF (f' :*: g) :<-: x) = upF (f' :<-: x) :*: g
  upF (RF (f :*: g') :<-: x) = f :*: upF (g' :<-: x)

теперь downF работает аналогично как это было для сумм, за исключением того, что мы должны исправить контекст молнии не только тегом (чтобы показать, в какую сторону мы пошли), но и нетронутым другим компонентом.

  downF (f :*: g)
    =    fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x) (downF f)
    :*:  fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x) (downF g)

но aroundF это массивный мешок смеха. Какую бы сторону мы ни посещали в настоящее время, у нас есть два варианта:

  1. движение aroundF с той стороны.
  2. движение upF С той стороны и downF в другую сторону.

каждый случай требует мы должны использовать операции для подструктуры, а затем исправить контексты.

  aroundF z@(LF (f' :*: g) :<-: (x :: x)) =
    LF (fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x)
          (cxF $ aroundF (f' :<-: x :: ZF f x))
        :*: fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x) (downF g))
    :<-: z
    where f = upF (f' :<-: x)
  aroundF z@(RF (f :*: g') :<-: (x :: x)) =
    RF (fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x) (downF f) :*:
        fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x)
          (cxF $ aroundF (g' :<-: x :: ZF g x)))
    :<-: z
    where g = upF (g' :<-: x)

Фух! Полиномы являются дифференцируемыми, и таким образом дать нам comonads.

Мда. Все это немного абстрактно. Поэтому я добавил deriving Show везде, где мог, и бросил в

deriving instance (Show (DF f x), Show x) => Show (ZF f x)

что позволило следующее взаимодействие (убранное вручную)

> downF (IF 1 :*: IF 2)
IF (LF (KF () :*: IF 2) :<-: 1) :*: IF (RF (IF 1 :*: KF ()) :<-: 2)

> fmap aroundF it
IF  (LF (KF () :*: IF (RF (IF 1 :*: KF ()) :<-: 2)) :<-: (LF (KF () :*: IF 2) :<-: 1))
:*:
IF  (RF (IF (LF (KF () :*: IF 2) :<-: 1) :*: KF ()) :<-: (RF (IF 1 :*: KF ()) :<-: 2))

упражнение показать, что состав дифференцируемых функторов дифференцируемый, используя правило цепи.

сладкий! Теперь мы можем идти домой? Конечно, нет. Мы не дифференцировали ни одного рекурсивные структур пока нет.

создание рекурсивных функторов из бифункторов

A Bifunctor, как объясняет существующая литература по универсальному программированию типов данных (см. работу Патрика Янссона и Йохана Юринга или отличные лекционные заметки Джереми Гиббонса), это конструктор типов с двумя параметры, соответствующие двум видам подструктуры. Мы должны быть в состоянии "нанести на карту" оба.

class Bifunctor b where
  bimap :: (x -> x') -> (y -> y') -> b x y -> b x' y'

можно использовать Bifunctors, чтобы дать структуру узлов рекурсивных контейнеров. Каждый узел имеет подузлы и элементов. Это могут быть только два вида субструктуры.

data Mu b y = In (b (Mu b y) y)

посмотреть? Мы "завязываем рекурсивный узел" в b ' s первый аргумент, и сохранить параметр y во втором. Соответственно, мы получаем один раз все!--122-->

instance Bifunctor b => Functor (Mu b) where
  fmap f (In b) = In (bimap (fmap f) f b)

чтобы использовать это, нам понадобится набор Bifunctor экземпляров.

В Bifunctor Комплект

константы это bifunctorial.

newtype K a x y = K a

instance Bifunctor (K a) where
  bimap f g (K a) = K a

вы можете сказать, что я написал этот бит первым, потому что идентификаторы короче, но это хорошо, потому что код длиннее.

переменные это bifunctorial.

нам нужны бифункторы, соответствующие одному параметру или другие, поэтому я сделал тип данных, чтобы отличить их, а затем определил подходящий GADT.

data Var = X | Y

data V :: Var -> * -> * -> * where
  XX :: x -> V X x y
  YY :: y -> V Y x y

, что составляет V X x y копия x и V Y x y копия y. Соответственно

instance Bifunctor (V v) where
  bimap f g (XX x) = XX (f x)
  bimap f g (YY y) = YY (g y)

суммы и продукты из бифункторов являются бифункторами

data (:++:) f g x y = L (f x y) | R (g x y) deriving Show

instance (Bifunctor b, Bifunctor c) => Bifunctor (b :++: c) where
  bimap f g (L b) = L (bimap f g b)
  bimap f g (R b) = R (bimap f g b)

data (:**:) f g x y = f x y :**: g x y deriving Show

instance (Bifunctor b, Bifunctor c) => Bifunctor (b :**: c) where
  bimap f g (b :**: c) = bimap f g b :**: bimap f g c

до сих пор, так шаблонно, но теперь мы можем определить такие вещи, как

List = Mu (K () :++: (V Y :**: V X))

Bin = Mu (V Y :**: (K () :++: (V X :**: V X)))

если вы хотите использовать эти типы для фактических данных и не пойти слепой в пуантилистской традиции Жоржа Сера, используйте pattern синонимы.

а как же молнии? Как мы докажем это?--81--> является дифференцируемой? Мы должны показать это b дифференцируется в и переменные. Лязг! Пришло время узнать о частичной дифференциации.

частные производные бифункторов

поскольку у нас есть две переменные, мы должны иметь возможность говорить о них иногда коллективно, иногда индивидуально. Нам понадобится семья синглтонов:--122-->

data Vary :: Var -> * where
  VX :: Vary X
  VY :: Vary Y

теперь мы можем сказать, что означает для Бифунктора иметь частные производные по каждой переменной и дать соответствующее понятие молнии.

class (Bifunctor b, Bifunctor (D b X), Bifunctor (D b Y)) => Diff2 b where
  type D b (v :: Var) :: * -> * -> *
  up      :: Vary v -> Z b v x y -> b x y
  down    :: b x y -> b (Z b X x y) (Z b Y x y)
  around  :: Vary v -> Z b v x y -> Z b v (Z b X x y) (Z b Y x y)

data Z b v x y = (:<-) {cxZ :: D b v x y, elZ :: V v x y}

этой D операция должна знать, какая переменная должна быть целевой. Соответствующая молния Z b v говорит нам, что переменная v должен быть в фокусе. Когда мы "украшаем контекстом" , мы должны украсить x-элементы с X-контексты и y-элементы с Y-контекстах. Но в остальном та же история.

у нас есть две оставшиеся задачи: во-первых, показать, что наш набор бифункторов дифференцируема; во-вторых, показать, что Diff2 b позволяет установить Diff1 (Mu b).

дифференцирование набора Бифункторов

боюсь, что этот бит скорее неудобный, чем назидательный. Не стесняйтесь пропустить вперед.

константы как до.

instance Diff2 (K a) where
  type D (K a) v = K Void
  up _ (K q :<- _) = absurd q
  down (K a) = K a
  around _ (K q :<- _) = absurd q

в этом случае жизнь слишком коротка, чтобы развивать теорию уровня типа Кронекер-Дельта, поэтому я просто рассматривал переменные отдельно.

instance Diff2 (V X) where
  type D (V X) X = K ()
  type D (V X) Y = K Void
  up VX (K () :<- XX x)  = XX x
  up VY (K q :<- _)      = absurd q
  down (XX x) = XX (K () :<- XX x)
  around VX z@(K () :<- XX x)  = K () :<- XX z
  around VY (K q :<- _)        = absurd q

instance Diff2 (V Y) where
  type D (V Y) X = K Void
  type D (V Y) Y = K ()
  up VX (K q :<- _)      = absurd q
  up VY (K () :<- YY y)  = YY y
  down (YY y) = YY (K () :<- YY y)
  around VX (K q :<- _)        = absurd q
  around VY z@(K () :<- YY y)  = K () :<- YY z

для структурных случаев я счел полезным ввести помощник, позволяющий мне обрабатывать переменные равномерно.

vV :: Vary v -> Z b v x y -> V v (Z b X x y) (Z b Y x y)
vV VX z = XX z
vV VY z = YY z
я создал гаджеты для облегчения своего рода "перейти" необходимо для down и around. (Конечно, я видел, какие гаджеты мне нужны, как я был рабочий.)
zimap :: (Bifunctor c) => (forall v. Vary v -> D b v x y -> D b' v x y) ->
         c (Z b X x y) (Z b Y x y) -> c (Z b' X x y) (Z b' Y x y)
zimap f = bimap
  (\ (d :<- XX x) -> f VX d :<- XX x)
  (\ (d :<- YY y) -> f VY d :<- YY y)

dzimap :: (Bifunctor (D c X), Bifunctor (D c Y)) =>
         (forall v. Vary v -> D b v x y -> D b' v x y) ->
         Vary v -> Z c v (Z b X x y) (Z b Y x y) -> D c v (Z b' X x y) (Z b' Y x y)
dzimap f VX (d :<- _) = bimap
  (\ (d :<- XX x) -> f VX d :<- XX x)
  (\ (d :<- YY y) -> f VY d :<- YY y)
  d
dzimap f VY (d :<- _) = bimap
  (\ (d :<- XX x) -> f VX d :<- XX x)
  (\ (d :<- YY y) -> f VY d :<- YY y)
  d

и с тем, что много готовы пойти, мы можем измельчить детали. Суммы легко.

instance (Diff2 b, Diff2 c) => Diff2 (b :++: c) where
  type D (b :++: c) v = D b v :++: D c v
  up v (L b' :<- vv) = L (up v (b' :<- vv))
  down (L b) = L (zimap (const L) (down b))
  down (R c) = R (zimap (const R) (down c))
  around v z@(L b' :<- vv :: Z (b :++: c) v x y)
    = L (dzimap (const L) v ba) :<- vV v z
    where ba = around v (b' :<- vv :: Z b v x y)
  around v z@(R c' :<- vv :: Z (b :++: c) v x y)
    = R (dzimap (const R) v ca) :<- vV v z
    where ca = around v (c' :<- vv :: Z c v x y)

продукты тяжелая работа, поэтому я математик, а не инженер.

instance (Diff2 b, Diff2 c) => Diff2 (b :**: c) where
  type D (b :**: c) v = (D b v :**: c) :++: (b :**: D c v)
  up v (L (b' :**: c) :<- vv) = up v (b' :<- vv) :**: c
  up v (R (b :**: c') :<- vv) = b :**: up v (c' :<- vv)
  down (b :**: c) =
    zimap (const (L . (:**: c))) (down b) :**: zimap (const (R . (b :**:))) (down c)
  around v z@(L (b' :**: c) :<- vv :: Z (b :**: c) v x y)
    = L (dzimap (const (L . (:**: c))) v ba :**:
        zimap (const (R . (b :**:))) (down c))
      :<- vV v z where
      b = up v (b' :<- vv :: Z b v x y)
      ba = around v (b' :<- vv :: Z b v x y)
  around v z@(R (b :**: c') :<- vv :: Z (b :**: c) v x y)
    = R (zimap (const (L . (:**: c))) (down b):**:
        dzimap (const (R . (b :**:))) v ca)
      :<- vV v z where
      c = up v (c' :<- vv :: Z c v x y)
      ca = around v (c' :<- vv :: Z c v x y)

концептуально это так же, как и раньше, но с большей бюрократией. Я построил их с использованием технологии pre-type-hole, используя undefined как заглушка в местах, где я не был готов работать, и введение преднамеренной ошибки типа в одном месте (в любом учитывая время), где я хотел, полезный совет от typechecker. Вы тоже можете иметь проверку типов как опыт видеоигр, даже в Haskell.

молнии Подузла для рекурсивных контейнеров

частичная производная от b с уважением X рассказывает нам, как найти подузел на один шаг внутри узла, поэтому мы получаем традиционное понятие молнии.

data MuZpr b y = MuZpr
  {  aboveMu  :: [D b X (Mu b y) y]
  ,  hereMu   :: Mu b y
  }

мы можем увеличить весь путь до корня путем повторного подключения X должностное положение.

muUp :: Diff2 b => MuZpr b y -> Mu b y
muUp (MuZpr {aboveMu = [], hereMu = t}) = t
muUp (MuZpr {aboveMu = (dX : dXs), hereMu = t}) =
  muUp (MuZpr {aboveMu = dXs, hereMu = In (up VX (dX :<- XX t))})

а нужно элемент-молнии.

элемент-молнии для фиксирующих точек бифункторов

каждый элемент находится где-то внутри узла. Этот узел находится под стеком X-производных. Но положение элемента в этом узле задается Y-производное. Мы получаем

data MuCx b y = MuCx
  {  aboveY  :: [D b X (Mu b y) y]
  ,  belowY  :: D b Y (Mu b y) y
  }

instance Diff2 b => Functor (MuCx b) where
  fmap f (MuCx { aboveY = dXs, belowY = dY }) = MuCx
    {  aboveY  = map (bimap (fmap f) f) dXs
    ,  belowY  = bimap (fmap f) f dY
    }

смело заявляю

instance Diff2 b => Diff1 (Mu b) where
  type DF (Mu b) = MuCx b

но прежде чем я начну операции, мне нужно какие-то обрывки.

я могу торговать данными между functor-zippers и bifunctor-zippers следующим образом:

zAboveY :: ZF (Mu b) y -> [D b X (Mu b y) y]  -- the stack of `X`-derivatives above me
zAboveY (d :<-: y) = aboveY d

zZipY :: ZF (Mu b) y -> Z b Y (Mu b y) y      -- the `Y`-zipper where I am
zZipY (d :<-: y) = belowY d :<- YY y

этого достаточно, чтобы я определил:

  upF z  = muUp (MuZpr {aboveMu = zAboveY z, hereMu = In (up VY (zZipY z))})

то есть мы идем вверх, сначала собирая узел, где находится элемент, превращая элемент-молнию в подузел-молнию, а затем масштабируя весь путь, как указано выше.

далее, я говорю

  downF  = yOnDown []

чтобы спуститься, начиная с пустого стека, и определить вспомогательная функция, которая идет down неоднократно снизу стопки:

yOnDown :: Diff2 b => [D b X (Mu b y) y] -> Mu b y -> Mu b (ZF (Mu b) y)
yOnDown dXs (In b) = In (contextualize dXs (down b))

теперь down b только принимает нас внутри узла. Молнии, которые нам нужны, также должны нести контекст узла. Вот что!--102--> тут:

contextualize :: (Bifunctor c, Diff2 b) =>
  [D b X (Mu b y) y] ->
  c (Z b X (Mu b y) y) (Z b Y (Mu b y) y) ->
  c (Mu b (ZF (Mu b) y)) (ZF (Mu b) y)
contextualize dXs = bimap
  (\ (dX :<- XX t) -> yOnDown (dX : dXs) t)
  (\ (dY :<- YY y) -> MuCx {aboveY = dXs, belowY = dY} :<-: y)

для каждого Y-позиция, мы должны дать элемент-молния, так что хорошо, что мы знаем весь контекст dXs вернуться к корню, а также dY который описывает, как элемент сидит в своем узле. Для каждого X-позиция, есть еще одно поддерево для изучения, поэтому мы растем стек и продолжаем идти!

остается только переключить фокус. Мы можем остаться на месте, или спуститься с того места, где мы находимся, или подняться, или подняться, а затем спуститься по какой-нибудь другой тропе. Здесь идет.

  aroundF z@(MuCx {aboveY = dXs, belowY = dY} :<-: _) = MuCx
    {  aboveY = yOnUp dXs (In (up VY (zZipY z)))
    ,  belowY = contextualize dXs (cxZ $ around VY (zZipY z))
    }  :<-: z

как всегда, существующий элемент заменен всей застежкой-молнией. Для belowY часть, мы смотрим, куда еще мы можем пойти в существующем узле: мы найдем любой альтернативный элемент Y-позиции или дальше X - подузлы для изучения, поэтому мы contextualise них. Для aboveY часть, мы должны работать наш путь вверх по стеку из X-производные после сборки узла, который мы посещали.

yOnUp :: Diff2 b => [D b X (Mu b y) y] -> Mu b y ->
         [D b X (Mu b (ZF (Mu b) y)) (ZF (Mu b) y)]
yOnUp [] t = []
yOnUp (dX : dXs) (t :: Mu b y)
  =  contextualize dXs (cxZ $ around VX (dX :<- XX t))
  :  yOnUp dXs (In (up VX (dX :<- XX t)))

на каждом шагу, мы можем либо повернуть куда-то еще это around, или продолжайте подниматься.

и это все! Я не дал формального доказательства законов, но мне кажется, что операции тщательно поддерживают контекст правильно, как они ползают структуру.

что мы узнали?

Дифференцируемость индуцирует понятия вещи в ее контексте, индуцируя комонадическую структуру, где extract дает вам вещь и duplicate исследует контекст, ища другие вещи для контекстуализации. Если у нас есть соответствующая дифференциальная структура для узлов, мы можем разработать дифференциальную структуру для целых деревьев.

Oh, и обрабатывать каждое индивидуальное arity типа конструктор отдельно вопиюще ужасен. Лучший способ-работать с функторами между индексированными наборами

f :: (i -> *) -> (o -> *)

где мы делаем o различные виды хранения структуры i различные виды элементов. Это закрытые под Якобианской конструкцией

J f :: (i -> *) -> ((o, i) -> *)

где каждый из полученных (o, i)-structures является частичной производной, рассказывающей вам, как сделать i-элемент-отверстие в элементе o-структуры. Но это уже в другой раз.


на Comonad экземпляр для молнии не

instance (Diff t, Diff (D t)) => Comonad (Zipper t) where
    extract = here
    duplicate = fmap outOf . inTo

здесь outOf и inTo приехали из Diff экземпляр . Приведенный выше экземпляр нарушает Comonad закон fmap extract . duplicate == id. Вместо этого он ведет себя так:

fmap extract . duplicate == \z -> fmap (const (here z)) z

Diff (молния t)

на Diff экземпляр Zipper обеспечивается путем идентификации их как продуктов и повторного использования кода для продуктов (ниже).

-- Zippers are themselves products
toZipper :: (D t :*: Identity) a -> Zipper t a
toZipper (d :*: (Identity h)) = Zipper d h

fromZipper :: Zipper t a -> (D t :*: Identity) a
fromZipper (Zipper d h) = (d :*: (Identity h))

дали изоморфизм между типами данных и изоморфизм между их производными, мы можем повторно использовать один тип inTo и outOf для других.

inToFor' :: (Diff r) =>
            (forall a.   r a ->   t a) ->
            (forall a.   t a ->   r a) ->
            (forall a. D r a -> D t a) ->
            (forall a. D t a -> D r a) ->
            t a -> t (Zipper t a)
inToFor' to from toD fromD = to . fmap (onDiff toD) . inTo . from

outOfFor' :: (Diff r) =>
            (forall a.   r a ->   t a) ->
            (forall a.   t a ->   r a) ->
            (forall a. D r a -> D t a) ->
            (forall a. D t a -> D r a) ->
            Zipper t a -> t a
outOfFor' to from toD fromD = to . outOf . onDiff fromD

для типов, которые являются только newTypes для существующего Diff экземпляр, их производные одного типа. Если мы скажем type checker об этом равенстве типов D r ~ D t, мы можем воспользоваться этим вместо обеспечения изоморфизма для производных.

inToFor :: (Diff r, D r ~ D t) =>
           (forall a. r a -> t a) ->
           (forall a. t a -> r a) ->
           t a -> t (Zipper t a)
inToFor to from = inToFor' to from id id

outOfFor :: (Diff r, D r ~ D t) =>
            (forall a. r a -> t a) ->
            (forall a. t a -> r a) ->
            Zipper t a -> t a
outOfFor to from = outOfFor' to from id id

оборудованный с этими инструментами, мы можем повторно использовать the Diff экземпляр для продуктов для реализации Diff (Zipper t)

-- This requires undecidable instances, due to the need to take D (D t)
instance (Diff t, Diff (D t)) => Diff (Zipper t) where
    type D (Zipper t) = D ((D t) :*: Identity)
    -- inTo :: t        a -> t        (Zipper  t         a)
    -- inTo :: Zipper t a -> Zipper t (Zipper (Zipper t) a)
    inTo = inToFor toZipper fromZipper
    -- outOf :: Zipper  t         a -> t        a
    -- outOf :: Zipper (Zipper t) a -> Zipper t a
    outOf = outOfFor toZipper fromZipper

шаблон

чтобы фактически использовать представленный здесь код, нам нужны некоторые расширения языка, импорт и повторное изложение предлагаемой проблемы.

{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RankNTypes #-}

import Control.Monad.Identity
import Data.Proxy
import Control.Comonad

data Zipper t a = Zipper { diff :: D t a, here :: a }

onDiff :: (D t a -> D u a) -> Zipper t a -> Zipper u a
onDiff f (Zipper d a) = Zipper (f d) a

deriving instance Diff t => Functor (Zipper t)
deriving instance (Eq (D t a), Eq a) => Eq (Zipper t a)
deriving instance (Show (D t a), Show a) => Show (Zipper t a)

class (Functor t, Functor (D t)) => Diff t where
  type D t :: * -> *
  inTo  :: t a -> t (Zipper t a)
  outOf :: Zipper t a -> t a

продукты, суммы и константы

на Diff (Zipper t) экземпляр полагается на реализации Diff продукты :*:, суммы :+: константы Identity, и ноль Proxy.

data (:+:) a b x = InL (a x) | InR (b x)
    deriving (Eq, Show)
data (:*:) a b x = a x :*: b x
    deriving (Eq, Show)

infixl 7 :*:
infixl 6 :+:

deriving instance (Functor a, Functor b) => Functor (a :*: b)

instance (Functor a, Functor b) => Functor (a :+: b) where
    fmap f (InL a) = InL . fmap f $ a
    fmap f (InR b) = InR . fmap f $ b


instance (Diff a, Diff b) => Diff (a :*: b) where
    type D (a :*: b) = D a :*: b :+: a :*: D b
    inTo (a :*: b) = 
        (fmap (onDiff (InL . (:*: b))) . inTo) a :*:
        (fmap (onDiff (InR . (a :*:))) . inTo) b
    outOf (Zipper (InL (a :*: b)) x) = (:*: b) . outOf . Zipper a $ x
    outOf (Zipper (InR (a :*: b)) x) = (a :*:) . outOf . Zipper b $ x

instance (Diff a, Diff b) => Diff (a :+: b) where
    type D (a :+: b) = D a :+: D b
    inTo (InL a) = InL . fmap (onDiff InL) . inTo $ a
    inTo (InR b) = InR . fmap (onDiff InR) . inTo $ b
    outOf (Zipper (InL a) x) = InL . outOf . Zipper a $ x
    outOf (Zipper (InR a) x) = InR . outOf . Zipper a $ x

instance Diff (Identity) where
    type D (Identity) = Proxy
    inTo = Identity . (Zipper Proxy) . runIdentity
    outOf = Identity . here

instance Diff (Proxy) where
    type D (Proxy) = Proxy
    inTo = const Proxy
    outOf = const Proxy

Bin Пример

я поставил Bin пример как изоморфизм к сумме произведений. Нам нужна не только его производная, но и вторая производная

newtype Bin   a = Bin   {unBin   ::      (Bin :*: Identity :*: Bin :+: Identity)  a}
    deriving (Functor, Eq, Show)
newtype DBin  a = DBin  {unDBin  ::    D (Bin :*: Identity :*: Bin :+: Identity)  a}
    deriving (Functor, Eq, Show)
newtype DDBin a = DDBin {unDDBin :: D (D (Bin :*: Identity :*: Bin :+: Identity)) a}
    deriving (Functor, Eq, Show)

instance Diff Bin where
    type D Bin = DBin
    inTo  = inToFor'  Bin unBin DBin unDBin
    outOf = outOfFor' Bin unBin DBin unDBin

instance Diff DBin where
    type D DBin = DDBin
    inTo  = inToFor'  DBin unDBin DDBin unDDBin
    outOf = outOfFor' DBin unDBin DDBin unDDBin

пример данных из предыдущий ответ is

aTree :: Bin Int    
aTree =
    (Bin . InL) (
        (Bin . InL) (
            (Bin . InR) (Identity 2)
            :*: (Identity 1) :*:
            (Bin . InR) (Identity 3)
        )
        :*: (Identity 0) :*:
        (Bin . InR) (Identity 4)
    )

не экземпляр Comonad

на Bin пример выше содержит пример fmap outOf . inTo быть правильной реализацией duplicate для Zipper t. В частности, он обеспечивает пример fmap extract . duplicate = id права:

fmap ( \z -> (fmap extract . duplicate) z == z) . inTo $ aTree

который оценивает (обратите внимание, как он полон Falses везде, любой False было бы достаточно, чтобы опровергнуть закон)

Bin {unBin = InL ((Bin {unBin = InL ((Bin {unBin = InR (Identity False)} :*: Identity False) :*: Bin {unBin = InR (Identity False)})} :*: Identity False) :*: Bin {unBin = InR (Identity False)})}

inTo aTree - это дерево с той же структурой, что и aTree, но везде, где было значение, вместо этого есть молния со значением, а остальная часть дерева со всеми исходными значениями нетронута. fmap (fmap extract . duplicate) . inTo $ aTree также дерево с тем же структура как aTree, но везде было значение, вместо этого есть молния со значением, а остальная часть дерева со всеми значениями, замененными тем же значением. Другими словами:

fmap extract . duplicate == \z -> fmap (const (here z)) z

полный набор тестов для всех трех Comonad законы extract . duplicate == id, fmap extract . duplicate == id и duplicate . duplicate == fmap duplicate . duplicate и

main = do
    putStrLn "fmap (\z -> (extract . duplicate) z == z) . inTo $ aTree"
    print   . fmap ( \z -> (extract . duplicate) z == z) . inTo $ aTree    
    putStrLn ""
    putStrLn  "fmap (\z -> (fmap extract . duplicate) z == z) . inTo $ aTree"
    print    . fmap ( \z -> (fmap extract . duplicate) z == z) . inTo $ aTree    
    putStrLn ""
    putStrLn "fmap (\z -> (duplicate . duplicate) z) == (fmap duplicate . duplicate) z) . inTo $ aTree"
    print   . fmap ( \z -> (duplicate . duplicate) z == (fmap duplicate . duplicate) z) . inTo $ aTree

учитывая бесконечно дифференцируемую Diff класс:

class (Functor t, Functor (D t)) => Diff t where
    type D t :: * -> *
    up :: Zipper t a -> t a
    down :: t a -> t (Zipper t a)  
    -- Require that types be infinitely differentiable
    ddiff :: p t -> Dict (Diff (D t))

around может быть написано в терминах up и down на Zipper ' s diffС derivitive, по существу, как

around z@(Zipper d h) = Zipper ctx z
    where
        ctx = fmap (\z' -> Zipper (up z') (here z')) (down d)

на Zipper t a состоит из D t a и a. Мы идем!--7--> на D t a, получив D t (Zipper (D t) a) с молнией в каждом отверстии. Эти молнии состоят из D (D t) a и a это было в яме. Мы идем!--6--> каждый из них, получив D t a и обрезать его с помощью a это было в яме. А D t a и a сделать Zipper t a, давая нам D t (Zipper t a), который является контекстом, необходимым для Zipper t (Zipper t a).

на Comonad экземпляр, то просто

instance Diff t => Comonad (Zipper t) where
    extract   = here
    duplicate = around

захват производной Diff словарь требует некоторых дополнительных сантехники, которые могут быть сделаны с данными.Ограничение или в терминах метод, представленный в ответ

around :: Diff t => Zipper t a -> Zipper t (Zipper t a)
around z = Zipper (withDict d' (fmap (\z' -> Zipper (up z') (here z')) (down (diff z)))) z
    where
        d' = ddiff . p' $ z
        p' :: Zipper t x -> Proxy t
        p' = const Proxy