Молния 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
это массивный мешок смеха. Какую бы сторону мы ни посещали в настоящее время, у нас есть два варианта:
- движение
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)
Фух! Полиномы являются дифференцируемыми, и таким образом дать нам 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'
можно использовать Bifunctor
s, чтобы дать структуру узлов рекурсивных контейнеров. Каждый узел имеет подузлы и элементов. Это могут быть только два вида субструктуры.
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
который оценивает (обратите внимание, как он полон False
s везде, любой 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