일반적으로 지퍼 코 모나드


80

모든 컨테이너 유형이 주어지면 (요소 중심) 지퍼를 형성 할 수 있으며이 구조가 Comonad임을 알 수 있습니다. 이것은 최근 다음 유형에 대한 또 다른 스택 오버플로 질문 에서 훌륭하게 자세히 탐구되었습니다 .

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

그것은의 경우 ZipA는 Comonad해당 인스턴스의 구성이 약간 털이 있지만. 즉, Zip완전히 기계적으로 파생 될 수 있으며 Tree이러한 방식으로 파생 된 모든 유형은 자동으로 a 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 스레드와 Conal Elliott의 블로그에 표시되었습니다. 이 클래스는 다양한 핵심 대수 유형에 대해 인스턴스화 될 수 있으므로 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) }
  ...

불행히도 나는 그러한 사례를 작성하는 운이 없었습니다. 는 IS inTo/ outOf서명이 충분? 유형을 제한하는 데 필요한 다른 것이 있습니까? 이 경우도 가능합니까?



Difffor Either및 구현에 대한 참조가 (,)있습니까? 확인하고 싶은 순진한 간단한 해결책이 있습니다.
Cirdec

@Cirdec 반드시 Either에 대해 구현하고 싶지는 않지만 대신 Either1 f g x = Inl (f x) | Inr (g x). Conal의 블로그 에는 자세한 내용이 있습니다.
J. Abrahamson

실제로이 Either프레임 워크에서는 구현할 수 없습니다 (그리고이 질문에 대한 진정한 답변이이 문제를 해결하기를 바랍니다) . Zipper최소한 하나의 목표 값을 가리킬 수 있다고 가정합니다 . 본격적으로 "비어있을"수있는 유형에는 불가능합니다.
J. Abrahamson

3
@Patrick이 질문은 상당히 고급 Haskell 기능을 기반으로하지만 실제로 다소 정확합니다. 그리고 Cirdec의 마지막 답변은 그리 길지 않습니다. 양돈 꾼이 매우 철저하게 대답하는 습관이 있다는 것은 대부분의 사람들이 높이 평가하는 다른 문제입니다.
Ørjan Johansen

답변:


113

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지퍼에 대해 수행 할 수있는 탐색의 종류 를 설명합니다 ( "왼쪽"및 "오른쪽"이라는 개념이 없습니다. 이에 대해서는 Clowns and Jokers 문서 참조). 우리는 "위로"가서 그 구멍에 요소를 꽂아 구조를 재 조립할 수 있습니다. 우리는 "아래로"가서 주어진 구조에서 요소를 방문하는 모든 방법을 찾을 수 있습니다. 우리는 모든 요소를 ​​컨텍스트로 장식합니다. 우리는 "둘러서", 기존 지퍼를 가져 와서 컨텍스트에 따라 각 요소를 장식 할 수 있으므로 초점을 다시 맞추는 모든 방법 (및 현재 초점을 유지하는 방법)을 찾습니다.

자,이 유형 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대한 재귀 호출을 명확하게하기 위해을 사용해야 했습니다 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

한 쌍의 요소에 초점을 맞추려면 왼쪽에 초점을 맞추고 오른쪽은 그대로 두거나 그 반대의 경우도 마찬가지입니다. Leibniz의 유명한 제품 규칙은 단순한 공간 직관에 해당합니다!

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에서 재귀 펑터 만들기

Bifunctor길이가 하부의 두 종류에 해당하는 두 개의 매개 변수와 타입 생성자이다에서, 데이터 타입 일반적인 프로그래밍에 대한 기존의 문헌으로 (패트릭 얀손과 요한 Jeuring, 또는 제레미 기븐스에 의한 우수한 강의 노트에 의해 참조 작업)을 설명합니다. 둘 다 "매핑"할 수 있어야합니다.

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

식별자가 더 짧기 때문에이 비트를 먼저 썼다고 말할 수 있지만 코드가 더 길기 때문에 좋습니다.

변수 는 이중 기능입니다.

하나 또는 다른 매개 변수에 해당하는 bifunctor가 필요하므로이를 구별하기 위해 데이터 유형을 만든 다음 적절한 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의 사본 xV 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)))

이러한 유형을 실제 데이터에 사용하고 Georges Seurat의 점진적 전통에서 눈을 멀게하지 않으려면 패턴 동의어를 사용하십시오 .

하지만 지퍼는 어떻습니까? 그것이 Mu b차별화 될 수 있음을 어떻게 보여줄 까요? 우리는 그것이 변수 b에서 구별 될 수 있음을 보여줄 필요가 있습니다. 그 소리! 부분 미분에 대해 배울 때입니다.

bifunctor의 부분 도함수

우리는 두 가지 변수를 가지고 있기 때문에 때때로 집단적으로 그리고 다른 시간에는 개별적으로 이야기 할 수 있어야합니다. 싱글 톤 패밀리가 필요합니다.

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

이제 Bifunctor가 각 변수에서 편도 함수를 갖는 것이 무엇을 의미하는지 말할 수 있고 이에 상응하는 지퍼 개념을 제공 할 수 있습니다.

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와 -elements X-contexts과 y와 -elements Y-contexts을. 그러나 그렇지 않으면 같은 이야기입니다.

나머지 두 가지 작업이 있습니다. 첫째, bifunctor 키트가 차별화 될 수 있음을 보여줍니다. 둘째,이를 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

이 경우, Kronecker-delta 유형 수준의 이론을 개발하기에는 인생이 너무 짧아서 변수를 별도로 처리했습니다.

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 하고, 내가 일할 준비가되지 않은 곳에서 스텁으로 하고, typechecker로부터 유용한 힌트를 원했던 한곳 (언제든지)에 의도적 인 유형 오류를 도입했습니다. . Haskell에서도 비디오 게임 경험으로 타입 체킹을 할 수 있습니다.

재귀 컨테이너 용 서브 노드 지퍼

에 대한 편미분 은 노드 내에서 한 단계 하위 노드를 찾는 방법 bX알려주므로 기존의 지퍼 개념을 얻습니다.

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-derivative 로 제공됩니다 . 우리는

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-positions 또는 추가 X탐구하는 -subnodes을 우리, contextualise그들. 를 들어 aboveY부분, 우리는 스택까지 우리의 방식으로 다시 일해야 X우리가 방문했던 노드를 재 조립 한 후 -derivatives합니다.

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종류의 요소를 저장하는 다른 종류의 구조를 만듭니다 . 이들은 Jacobian 구조 로 닫힙니다.

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

각각의 결과 (o, i)구조는 편미분 i으로 o구조 에서 요소 구멍 을 만드는 방법을 알려줍니다 . 그러나 그것은 다른 시간 동안 의존적으로 입력 된 재미입니다.


2
"비디오 게임으로서의 유형 검사"를 사용하거나 오히려 유형에 대해서만 추론하여 Comonad레벨을 통과 했지만 대체 엔딩에 도달 할 수있었습니다. 게임을하면서 흥미롭고 까다로운 수준에 도달했습니다. typechecker는 구멍의 유형이 a -> a(큰 긴 유형의 경우 a)라고 말했지만 구멍을 채우지 id못했습니다. 문제는 a ~ D t ~ D r실제로 함수가 필요 D r -> D t했고 typechecker에 증명을 제공해야한다는 것입니다.D r ~ D t .
Cirdec

3
따라서 ScopedTypeVariables를주의 깊게 사용합니다. ghc가 SPJ의 목소리로 "No no no, 추측 하고 싶습니다 !" 라고 말하는 순간을 위해 하지만 추측은 너무 어렵습니다.
pigworker

12
짧은 대답은 Diff도 around서명에 필요하다는 것입니다. 긴 대답은 언제나처럼 환상적으로 눈을 뜨게하는 것입니다. 잠시 시간을내어이 글을 써 주셔서 감사합니다!
J. Abrahamson

1
에 가서 조각 downaround동일합니다. 우리가 뭔가에 의해 제품을 예 그들 모두를 지정 할 수 있어야한다 것 같다 descend f (a :*: b) = pure (:*:) <*> f (InL . (:*: b)) a <*> f (InR . (a :*:)) bdescend의 라인을 따라 종류가 있습니다 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에서 코드를 재사용하고 제 유도체 updown같은 추가적인 추상화 없이도 Applicative그것을 캡처한다.
Cirdec

12

Comonad지퍼에 대한 인스턴스입니다 하지

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

어디서 outOf그리고inToDiff인스턴스 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))

데이터 유형 간의 동형 및 파생 항목 간의 동형이 주어지면 한 유형의 inTooutOf다른 .

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 인스턴스의 경우 파생물은 동일한 유형입니다. 유형 검사기에 해당 유형 equality에 대해 알려 주면 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)
    )

Comonad 인스턴스가 아닙니다.

Bin위 의 예 fmap outOf . inToduplicatefor 의 올바른 구현에 대한 반례를 제공합니다 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 aTreeaTree는와 동일한 구조를 가진 나무 이지만 값이있는 모든 곳에 값이있는 지퍼가 있고 원래 값이 모두 그대로있는 나머지 트리가 있습니다. 또한와 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
updownConal의 블로그에서와 동일 into하고 outof.
J. Abrahamson

@pigworker가 1 년 전에 내려 가려고했던 것과 같은 길로 내려 가려고했던 것을 볼 수 있습니다. 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측면에서 쓸 수 updownZipperdiff의 derivitive, 본질적으로

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

Zipper t a(A)의 구성 D t aa. 우리는 이동 downD t a을 받고, D t (Zipper (D t) a)모든 구멍에 지퍼. 그 지퍼는 구멍에 있던 a D (D t) a와으로 구성되어 있습니다 a. 우리 up는 그들 각각을 가서 구멍에 있던 D t a것과 짝을 이룹니다 a. A D t aa메이크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사전을 캡처하려면 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 . 커스텀 지퍼를 땅에 박고 자동으로 사용할 수 있는지 확인하는 것이 around좋습니다.
J. Abrahamson 2014 년

2
첫 번째 around는 또한 방법을 사용 around :: (Diff t, Diff (D t)) => Zipper t a -> Zipper t (Zipper t a)하거나 사용하지 않고 유형 검사 ddiff를 수행하며 Comonad인스턴스에 대해서도 유사 하므로 두 배의 차별화 가능성으로 충분합니다.
Ørjan Johansen 2014 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.