Комонады Застежки-Молнии, Вообще
учитывая любой тип контейнера, мы можем сформировать (ориентированную на элемент) молнию и знать, что эта структура является Комонадой. Это было недавно исследовано в замечательных деталях в другом вопросе переполнения стека для следующего типа:
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 и (I поверьте) любой тип, полученный таким образом, автоматически является 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 и в блоге Конала Эллиота. Этот класс может быть создан для различных основных типов алгебраических и таким образом, обеспечивается общая основа для разговора о производных ADTs.
Итак, в конечном счете, мой вопрос заключается в том, можем ли мы писать
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 ответов:
как childcatcher в 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.The личность функтор является дифференцируемой.
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для устранения неоднозначности рекурсивные вызовы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это огромный мешок смеха. Какую бы сторону мы сейчас ни посещали, у нас есть два варианта:
- движение
aroundFс той стороны.- движение
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))упражнение показать, что состав дифференцируемых функторов является дифференцируемый, используя правило цепи.
сладкий! Теперь мы можем идти домой? Конечно, нет. Мы не дифференцировали ни одного рекурсивные структур пока нет.
создание рекурсивных функторов из бифункторов
A
Bifunctor, как объясняет существующая литература по типам данных generic programming (см. работу Патрика Янссона и Йохана Джуринга или отличные лекционные заметки Джереми Гиббонса), это конструктор типов с двумя параметры, соответствующие двум видам подструктуры. Мы должны быть в состоянии "карты" как.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экземпляров.Бифункциональный Комплект
константы являются бифункциональными.
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)суммы и продукты из бифункторов являются бифункторами
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).дифференциация набора Бифункторов
я боюсь, что этот бит является скрипучим, а не назидательным. Не стесняйтесь пропустить вперед.
константы как до.
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 zdownи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только принимает нас внутри узла. Молнии, которые нам нужны, также должны нести контекст узла. Вот что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)-structures является частной производной, рассказывая вам, как сделатьi-элемент-отверстие в элементеo-структуры. Но это зависимо типизированная забава, в другой раз.
The
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)) zDiff (молния t)
The
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для типов, которые являются просто новыми типами для существующего
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продукты, суммы и константы
The
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 ProxyBin Example
я поставил
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) )не экземпляр Comonad
The
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' sdiffпроизводный, по сути какaround z@(Zipper d h) = Zipper ctx z where ctx = fmap (\z' -> Zipper (up z') (here z')) (down d)The
Zipper t aсостоит изD t aиa. Мы идемdowntheD t a, получивD t (Zipper (D t) a)С застежкой-молнией в каждом отверстии. Эти молнии состоит изD (D t) aиaэто было в дыре. Мы идемupкаждый из них, получивD t aи обрезать его сaэто было в дыре. АD t aиaсделатьZipper t a, давая намD t (Zipper t a), который является контекстом, необходимым дляZipper t (Zipper t a).The
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
Comments