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
fmap upF (downF xs) = fmap (const xs) xs
多項式関手は微分可能です
一定の関手は微分可能です。
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つの選択肢があります。
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))
演習連鎖律を使用して、微分可能ファンクターの合成が微分可能であることを示します。
甘い!今家に帰れますか?もちろん違います。再帰的構造はまだ区別されていません。
バイファンクターから再帰ファンクターを作成する
ABifunctor
データ型の一般的なプログラミングに関する既存の文献としては、(ジェレミー・ギボンズによってパトリックヤンソンとヨハンJeuring作品、または優れた講義ノートを参照)の長さで説明下部の2種類に対応する2つのパラメータを有するタイプのコンストラクタです。両方を「マッピング」できるはずです。
class Bifunctor b where
bimap :: (x -> x') -> (y -> y') -> b x y -> b x' y'
Bifunctor
sを使用して、再帰コンテナのノード構造を与えることができます。各ノードにはサブノードと要素があります。これらは、2種類の下位構造にすぎません。
data Mu b y = In (b (Mu b y) y)
見る?b
の最初の引数で「再帰的結び目を結び」、y
2番目の引数でパラメータを保持します。したがって、私たちはすべてのために一度取得します
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)))
これらのタイプを実際のデータに使用し、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]
zAboveY (d :<-: y) = aboveY d
zZipY :: ZF (Mu b) y -> Z b Y (Mu b y) y
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
。しかし、それは依存型の楽しみです。