Комони на блискавках, загалом


80

Враховуючи будь-який тип контейнера, ми можемо сформувати блискавку (орієнтовану на елементи) і знати, що ця структура є комонадою. Це нещодавно було детально досліджено в іншому запитанні щодо переповнення стеку для наступного типу:

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 та в блозі Кональ Елліот. Цей клас може бути створений для різних основних алгебраїчних типів і, таким чином, забезпечує загальну основу для розмови про похідні ADT.

Тож, зрештою, моє питання полягає в тому, чи можемо ми писати чи ні

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підпису? Чи потрібно ще щось обмежувати типи? Чи можливий цей примірник?


29
Дайте нам хвилинку ...
свинарник

Чи є у вас довідка про реалізацію Difffor Eitherта (,)? У мене є наївно просте можливе рішення, яке я хотів би перевірити.
Cirdec

@Cirdec Ви не обов'язково хочете реалізовувати це для будь-якого, а для Either1 f g x = Inl (f x) | Inr (g x). Блог Conal містить усі подробиці.
Дж. Абрахамсон,

Насправді, Eitherне може бути повністю реалізовано в цій структурі (і, сподіваємось, справжня відповідь на це питання вирішить цю проблему), оскільки Zipperпередбачається, що ви можете вказати принаймні на одне цільове значення. Насправді це неможливо для типів, які можуть бути "порожніми".
Дж. Абрахамсон,

3
@Patrick Це питання насправді є досить точним, хоча воно базується на досить вдосконалених функціях Haskell. І остання відповідь Цирдека не така вже й довга. Інша справа, що більшість людей оцінює, полягає в тому, що свинарник має звичку робити свої відповіді дуже ґрунтовними.
Ерхан Йогансен

Відповіді:


113

Подібно до ловця дітей у Чітті-Чітті-Банг-Бенг, що заманює дітей у полон солодощами та іграшками, вербувальники, які навчаються на бакалавраті фізики, люблять дуріти з бульбашками мила та бумерангами, але коли стукіт дверей зачиняється, це „Правильно, діти, час вчитися про часткову диференціацію! ". Я також. Не кажіть, що я вас не попередив.

Ось ще одне попередження: потрібен наступний код {-# 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 може залишатися на місці.

Сума зберігає диференційованість.

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щоб усунути неоднозначність рекурсивних дзвінків на 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)

Фу! Поліноми всі диференційовані і, таким чином, дають нам комонади.

Хм Це все трохи абстрактно. Тому я додав 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))

Вправа Покажіть, що склад диференційованих функторів диференціюється, використовуючи правило ланцюга .

Солодко! Чи можемо ми зараз додому? Звичайно, ні. Ми ще не диференціювали жодної рекурсивної структури.

Створення рекурсивних функторів з біфункторів

Як 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першому аргументі, а параметр зберігаємо yу другому. Відповідно, отримуємо раз і назавжди

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

Для цього нам знадобиться набір Bifunctorекземплярів.

Набір Bifunctor

Константи є біфункціональними.

newtype K a x y = K a

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

Ви можете сказати, що я написав цей біт першим, оскільки ідентифікатори коротші, але це добре, оскільки код довший.

Змінні є двофункціональними.

Нам потрібні біфункціонери, що відповідають тому чи іншому параметру, тому я створив тип даних для їх розрізнення, а потім визначив відповідний 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)

Суми і продукти з bifunctors є bifunctors

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)))

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

Але що з блискавками? Як ми покажемо, що Mu bможна диференціювати? Нам потрібно буде показати, що bдиференційовано в обох змінних. Кланг! Пора дізнатися про часткову диференціацію.

Часткові похідні біфункціонерів

Оскільки ми маємо дві змінні, нам потрібно мати можливість говорити про них інколи, а інколи - окремо. Нам знадобиться сімейство одиноких:

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).

Розрізнення набору Bifunctor

Я боюся, що цей біт є хитрим, а не повчальним. Не соромтеся пропускати далі.

Константи такі, як і раніше.

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)

Концептуально це так само, як і раніше, але з більшою бюрократією. Я побудував їх за технологією попереднього типу отвору, використовуючиundefined як заглушку в місцях, де я не був готовий працювати, і вводячи навмисну ​​помилку типу в одному місці (у будь-який момент часу), де я хотів отримати корисну підказку від перевірки друку . Ви також можете використовувати перевірку друку як досвід відеоігор, навіть у 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

але перш ніж розробляти операції, мені знадобляться шматочки.

Я можу торгувати даними між застібками-блискавками та біфункціональними застібками наступним чином:

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нас веде лише всередину вузла. Застібки-блискавки також повинні містити контекст вузла. Ось що contextualise:

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досліджує контекст, шукаючи інші речі для контекстуалізації. Якщо ми маємо відповідну диференціальну структуру для вузлів, ми можемо розробити диференціальну структуру для цілих дерев.

О, і ставитися до кожної окремої сутності конструктора типу окремо є відверто жахливим. Кращий спосіб - це робота з функторами між індексованими наборами

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

де ми робимо oрізні типи структур, що зберігають iрізні типи елементів. Вони закриті під будівництво Якобія

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

де кожна з отриманих (o, i)-структур є частковою похідною, яка розповідає вам, як зробити iдірку -елемент в o-структурі. Але це залежно від типу розваги, інший раз.


2
Використовуючи "перевірку типу як відеоігру", точніше, міркуючи лише про типи, я пройшов Comonadрівень, але міг дійти лише до альтернативного закінчення. Граючи в гру, я потрапив на цікавий і хитрий рівень. Машинка перевіряла тип отвору a -> a(для деяких великих довгих типів a), але заповнення отвору idне спрацювало. Проблема полягала в тому a ~ D t ~ D r, і мені насправді потрібна була функція, і мені D r -> D tпотрібно було надати перевірку друку підтвердження D r ~ D t.
Cirdec

3
отже, обережне використання ScopedTypeVariables для тих моментів, коли ghc каже (голосом SPJ) "Ні, ні, я хочу вгадати !" але вгадати занадто важко.
свинарник

12
Здається, коротка відповідь така, що Діфф також потребує aroundу своєму підписі. Довга відповідь, ну, фантастично відкриває очі, як завжди. Велике спасибі, що витратили хвилину на написання цього!
Дж. Абрахамсон,

1
Шматочки, які входять downі aroundє однаковими. Здається, ми повинні мати можливість вказати їх обидва, наприклад, для продуктів, наприклад, descend f (a :*: b) = pure (:*:) <*> f (InL . (:*: b)) a <*> f (InR . (a :*:)) bде- небудь, де descendє тип, подібний до Applicative (m t) => (forall f g. (Diff f, Diff g) => (D f a -> D g a) -> f a -> m g (f a)) -> t a -> m t (t a).
Cirdec

1
aroundможе бути записаний повністю в термінах down, upа друга похідна, повторне використання коду з upі downбез необхідності додаткової абстракції, як Applicativeйого захоплення.
Cirdec

12

ComonadПримірник для застібки - блискавки є НЕ

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

звідки outOfі inToпоходять з Diffінстанції Zipper tсамі по собі. Вищевказана інстанція порушує 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екземпляра, їх похідні є однаковими. Якщо ми розповімо перевірку типу про цю рівність типу 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

Оснащені цими інструментами, ми можемо повторно використовувати 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приклад як ізоморфізм суми добутків. Нам потрібна не лише його похідна, а й друга похідна

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

Прикладом даних з попередньої відповіді є

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

Не екземпляр Комонад

Наведений Binвище приклад наводить контра-приклад fmap outOf . inToправильної реалізації duplicatefor Zipper t. Зокрема, він наводить приклад fmap extract . duplicate = idзакону:

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

Яка оцінка до (зауважте, як Falseскрізь повно с, будь-якого 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 . duplicateIS

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

1
upі downз блогу Conal такі самі, як intoі outof.
Дж. Абрахамсон,

Я бачу, що @pigworker намагався піти тим самим шляхом, яким я намагаюся піти рік тому. stackoverflow.com/questions/14133121 / ...
Cirdec

8

Дано нескінченно диференційований 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", по суті як

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. Заходить , отримання із застібкою - блискавкою в кожній дірці. Ці блискавки складаються з і того, що було в отворі. Ми йдемо до кожного з них, дістаємо і розлучаємо його з тим, що було в отворі. A та make a , даючи нам a , що є контекстом, необхідним для a .downD t aD t (Zipper (D t) a)D (D t) aaupD t aaD t aaZipper t aD t (Zipper t a)Zipper t (Zipper t a)

ComonadПримірник , то просто

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

Захоплення Diffсловника похідної вимагає додаткової сантехніки, яку можна зробити за допомогою Data.Constraint або з точки зору методу, представленого у відповідній відповіді

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 

Дураючись із цим, здається, чудово працює: gist.github.com/tel/fae4f90f47a9eda0373b . Я був би акуратним, щоб побачити, чи можу я загнати на замовлення застібки-блискавки, а потім використати це для отримання автоматичних arounds.
J. Abrahamson,

2
Перший aroundтакож перевіряє тип без around :: (Diff t, Diff (D t)) => Zipper t a -> Zipper t (Zipper t a)і без ddiffметоду, і так само для Comonadекземпляру, тому подвійної диференціації здається достатньо.
Ørjan Johansen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.