ジッパーコモナド、一般的に


80

任意のコンテナタイプが与えられると、(要素に焦点を合わせた)ジッパーを形成し、この構造がコモナドであることがわかります。これは最近、次のタイプの別のStackOverflowの質問で非常に詳細に調査されました。

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であるため、これらのタイプとそのコマンドを一般的かつ自動的に構築できるのは当然だと思います。

ジッパー構造の一般性を実現するための1つの方法は、次のクラスとタイプのファミリーを使用することです。

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

これは(多かれ少なかれ)HaskellCafeのスレッドとConalElliottのブログに表示されています。このクラスは、さまざまなコア代数的タイプに対してインスタンス化できるため、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
私たちの分を与える...
pigworker

DiffforEitherとの実装に関するリファレンスはあり(,)ますか?確認したい単純な解決策があります。
Cirdec 2014

@Cirdec必ずしもEitherに実装する必要はありませんが、代わりにEither1 f g x = Inl (f x) | Inr (g x)Conalのブログに詳細があります。
J.アブラハム

実際にEitherZipper、少なくとも1つのターゲット値を指すことができると想定しているため、このフレームワークに完全に実装することはできません(そして、この質問に対する真の答えがこの問題に対処することを願っています)。本格的には、これは「空」になる可能性のあるタイプでは不可能です。
J.アブラハム

3
@Patrickこの質問は実際にはかなり正確ですが、非常に高度なHaskell機能に基づいています。そして、Cirdecの最後の答えはそれほど長くはありません。豚労働者が彼の答えを非常に徹底的にする習慣があるということは別の問題であり、ほとんどの人がそれを高く評価しています。
Ørjanヨハンセン

回答:


113

Chitty-Chitty-Bang-Bangのチャイルドキャッチャーのように、お菓子やおもちゃで子供たちを捕らえに誘い込みます。学部の物理学の採用担当者は、シャボン玉やブーメランをいじくり回すのが好きですが、ドアが閉まると、「そうです、子供たち、学ぶ時間です。偏微分について!」私も。私があなたに警告しなかったと言ってはいけません。

別の警告があります:次のコードは{-# LANGUAGE KitchenSink #-}、というよりも必要です

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

順不同。

微分可能な関手はcomonadicジッパーを与えます

とにかく、微分可能なファンクターとは何ですか?

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}

デリバティブを持つファンクターであり、ファンクターでもあります。導関数は、要素の1つの穴のコンテキストを表します。ジッパータイプZF f xは、1つの穴のコンテキストと穴の要素のペアを表します。

の操作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

些細な文脈で1つの要素があり、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は大笑いの袋です。現在訪問している側には、2つの選択肢があります。

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

演習連鎖律を使用して、微分可能ファンクターの合成が微分可能であることを示します。

甘い!今家に帰れますか?もちろん違います。再帰的構造はまだ区別されていません。

バイファンクターから再帰ファンクターを作成する

ABifunctorデータ型の一般的なプログラミングに関する既存の文献としては、(ジェレミー・ギボンズによってパトリックヤンソンとヨハンJeuring作品、または優れた講義ノートを参照)の長さで説明下部の2種類に対応する2つのパラメータを有するタイプのコンストラクタです。両方を「マッピング」できるはずです。

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

Bifunctorsを使用して、再帰コンテナのノード構造を与えることができます。各ノードにはサブノード要素があります。これらは、2種類の下位構造にすぎません。

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

見る?bの最初の引数で「再帰的結び目を結び」、y2番目の引数でパラメータを保持します。したがって、私たちはすべてのために一度取得します

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、のコピー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)

バイファンクターの合計はバイファンクターです

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両方の変数で微分可能であることを示す必要があります。クラン!偏微分について学ぶ時が来ました。

バイファンクターの偏導関数

変数が2つあるので、それらについてまとめて話すこともあれば、個別に話すこともできる必要があります。シングルトンファミリーが必要です。

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。しかし、そうでなければ、それは同じ話です。

残りの2つのタスクがあります。1つは、バイファンクターキットが微分可能であることを示すことです。第二に、それ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してこれらを構築し、作業する準備ができていない場所でスタブとして使用し、タイプチェッカーからの有用なヒントが必要な1つの場所(いつでも)に意図的なタイプエラーを導入しました。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))})

ただし、element -zippersが必要です。

バイファンクターのフィックスポイント用のエレメントジッパー

各要素はノード内のどこかにあります。そのノードは、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-positionに対して、element-zipperを指定する必要があるため、コンテキスト全体を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私たちが訪問したノードを再組み立てした後-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種類の要素を格納するさまざまな種類の構造を作成します。これらはヤコビアン構造の下で閉じられています

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

ここで、結果の各-structure(o, i)は偏導関数であり、i-structureに-element-holeを作成する方法を示しますo。しかし、それは依存型の楽しみです。


2
「ビデオゲームとしての型チェック」を使用して、またはむしろ型についてのみ推論して、私はComonadレベルを超えましたが、別の結末にしか到達できませんでした。ゲームをしていると、面白くてトリッキーなレベルに出くわしました。タイプチェッカーは、穴のタイプはa -> a(いくつかの大きな長いタイプの場合a)であると言いましたが、穴を埋めるidことは機能しませんでした。問題はそれでしたa ~ D t ~ D r、そして私は実際に関数D r -> D tを必要とし、そしてタイプチェッカーにの証明を提供する必要がありましたD r ~ D t
Cirdec 2014

3
したがって、ghcが(SPJの声で)「いやいやいや、推測したい!」と言う瞬間のために、ScopedTypeVariablesを注意深く使用してください。しかし、推測は難しすぎます。
pigworker 2014

12
簡単な答えは、Diffがaroundその署名にも必要であるということのようです。長い答えは、まあ、いつものように素晴らしく目を見張るものです。これを書くのに少し時間を費やしてくれてありがとう!
J.アブラハム

1
入る作品downaround同じです。たとえば、の行に沿ってタイプがあるdescend f (a :*: b) = pure (:*:) <*> f (InL . (:*: b)) a <*> f (InR . (a :*:)) bwhereのようなもので、製品の両方を指定できるはず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 2014

1
arounddownupおよび2次導関数の観点から完全に記述でき、コードをキャプチャするなどの追加の抽象化を必要updownせずに、コードを再利用しますApplicative
Cirdec 2014

12

Comonadジッパーのインスタンスはそうではありません

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

どこoutOfinToから来るDiffのインスタンスZipper tそれ自体。上記の事例はComonad法律に違反していますfmap extract . duplicate == id。代わりに、次のように動作します。

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

デフ(ジッパー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

既存のDiffインスタンスの単なるnewTypeである型の場合、それらの派生物は同じ型です。型チェッカーにその型の同等性について伝える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)インスタンスは、Difffor製品:*:、合計:+:、定数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例は、forのfmap outOf . inTo正しい実装であることに反例を示しています。特に、それは法律に対する反例を提供します:duplicateZipper tfmap 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

3つのすべてのための完全なテストスイートComonadの法律、extract . duplicate == idfmap 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そしてdownConalのブログと同じですintooutof
J.アブラハム

@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用語で記述することができupかつdownZipperdiff本質的として、さん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。私達は行くdownD t aなって、D t (Zipper (D t) a)すべての穴にジッパーで。それらのジッパーは、穴にあったD (D t) aとで構成aされています。私達は行くupはそれらのそれぞれに、を取得しD t a、それをa穴にあったものと比較します。AD t aamakeは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.アブラハム

2
1つ目aroundaround :: (Diff t, Diff (D t)) => Zipper t a -> Zipper t (Zipper t a)ddiffメソッドありとメソッドなしのタイプチェックであり、Comonadインスタンスについても同様であるため、2回の微分可能性で十分であるように思われます。
Ørjanヨハンセン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.