修正とMu同型


8

recursion-schemes、パッケージは、次の種類が定義されています。

newtype Fix f = Fix (f (Fix f))

newtype Mu f = Mu (forall a. (f a -> a) -> a)

それらは同型ですか?もしそうなら、どうやってそれを証明しますか?


3
関連性:Ed Kmettの再帰スキームパッケージのFix、Mu、Nuの違いは何ですか(答えがないのは、明確に記述された同型写像です)。
duplode

厳密な言葉で言うと(怠惰なため)Mu f < Fix f < Nu f
Haskellではい

2
同型について@duplode; Fix-to-はMu本質的であるcata一方で、Mu-to-がFixありますmu2fix (Mu x) = x Fix。トリッキーな部分は、これらが相互の逆であることを証明し、パラメトリック性を利用することです。
カイ

また、このカタを解くことができますcodewars.com/kata/folding-through-a-fixed-point
xgrommx

1
@xgrommx、厳密な文脈で、表現Fixできない用語の例は何Muですか?ISTM Fixは最小である必要があります(「データ構造」であり、ボトムを含めることができないため、直感的にわかります)
luqui

回答:


4

それらは同型ですか?

はい、Haskellでは同型です。その他の注意については、Ed Kmettの再帰スキームパッケージのFix、Mu、Nuの違いを参照してください。

もしそうなら、どうやってそれを証明しますか?

まず、変換を実行する関数を定義します。

muToFix :: Mu f -> Fix f
muToFix (Mu s) = s Fix

fixToMu :: Functor f => Fix f -> Mu f
fixToMu t = Mu (\alg -> cata alg t)

これらの関数が同型であることを示すには、次のことを示す必要があります。

muToFix . fixToMu = id
fixToMu . muToFix = id

Fixバック

同型の方向の1つは、他の方向よりもいくぶん簡単に外れます。

muToFix (fixToMu t) = t
muToFix (fixToMu t)  -- LHS
muToFix (Mu (\f -> cata f t))
(\f -> cata f t) Fix
cata Fix t  -- See below.
t  -- LHS = RHS

上記の最後の節cata Fix t = tは、次の定義で確認できますcata

cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg = alg . fmap (cata alg) . unfix

cata Fix t次に、ですFix (fmap (cata Fix) (unfix t))。帰納法を使用してt、少なくとも有限の場合、それがでなければならないことを示すことができますt(無限の構造ではより微妙になります。この回答の最後にある補遺を参照してください)。考慮すべき2つの可能性があります。

  • unfix t :: f (Fix f)空であり、掘り下げる再帰位置はありません。その場合、それはfmap absurd zsome と等しい必要があるためz :: f Void、次のようになります。

    cata Fix t
    Fix (fmap (cata Fix) (unfix t))
    Fix (fmap (cata Fix) (fmap absurd z))
    Fix (fmap (cata Fix . absurd) z)
    -- fmap doesn't do anything on an empty structure.
    Fix (fmap absurd z)
    Fix (unfix t)
    t
  • unfix t空ではありません。その場合、少なくとも、再帰的な位置にfmap (cata Fix)適用する以上のことはできないことがわかっていますcata Fix。ここでの帰納法の仮説は、そうすることでそれらの位置が変更されないままになるというものです。次に、

    cata Fix t
    Fix (fmap (cata Fix) (unfix t))
    Fix (unfix t)  -- Induction hypothesis.
    t

(結局のところ、これcata Fix = idFix :: f (Fix f) -> Fix x初期のF代数であることの帰結です。この証明のコンテキストでその事実に直接頼ることは、おそらくあまりにも多くのショートカットになるでしょう。)

Muバック

与えられたmuToFix . fixToMu = idfixToMu . muToFix = idいずれかを証明することで十分であることを証明するには:

  • それmuToFixは単射的です、または

  • それfixToMuは全容です。

2番目のオプションを使用して、関連する定義を確認します。

newtype Mu f = Mu (forall a. (f a -> a) -> a)

fixToMu :: Functor f => Fix f -> Mu f
fixToMu t = Mu (\alg -> cata alg t)

fixToMuつまり、全射的であることは、特定のが与えられた場合Functor f、型のすべての関数を特定のに対してforall a. (f a -> a) -> aとして定義できることを意味します。タスクは、関数をカタログ化し、それらすべてをその形式で表現できるかどうかを確認することになります。\alg -> cata alg tt :: Fix fforall a. (f a -> a) -> a

forall a. (f a -> a) -> a頼りにせずに関数を定義するにはどうすればよいfixToMuですか?何があっても、結果f a -> aを取得するには、引数として指定された代数を使用する必要がありますa。直接ルートは、それをあるf a値に適用することになります。主な注意点は、aポリモーフィックであるf aため、任意の選択に対して上記の値を想起させる必要があるということですa。それが実行可能な戦略である限り、それは実行可能な戦略ですf価値が存在するです。その場合、次のことができます。

fromEmpty :: Functor f => f Void -> forall a. (f a -> a) -> a
fromEmpty z = \alg -> alg (fmap absurd z)

表記をわかりやすくするために、forall a. (f a -> a) -> a関数の定義に使用できるもののタイプを定義しましょう。

data Moo f = Empty (f Void)

fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo (Empty z) = \alg -> alg (fmap absurd z)

直接ルート以外にも、可能性は1つだけあります。それを考えるとfありFunctor、我々は何とか持っている場合、f (Moo f)外側の下にある最初のアプリケーション、値を私たちは二度代数を適用することができますf層を介してfmapfromMoo

fromLayered :: Functor f => f (Moo f) -> forall a. (f a -> a) -> a
fromLayered u = \alg -> alg (fmap (\moo -> fromMoo moo alg) u)

forall a. (f a -> a) -> aからも作成できることを考えるとf (Moo f)、それらを次の場合として追加することは理にかなっていますMoo

data Moo f = Empty (f Void) | Layered (f (Moo f))

したがって、以下fromLayeredに組み込むことができますfromMoo

fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo = \case
    Empty z -> \alg -> alg (fmap absurd z)
    Layered u -> \alg -> alg (fmap (\moo -> fromMoo moo alg) u)

こうすることalgで、1つのfレイヤーの下に適用することから、再帰的に適用することにこっそりと移動したことに注意してください。alg、任意の数のfレイヤーの下。

次に、f Void値をLayeredコンストラクターに注入できることに注意してください。

emptyLayered :: Functor f => f Void -> Moo f
emptyLayered z = Layered (fmap absurd z)

つまり、実際にはEmptyコンストラクタは必要ありません。

newtype Moo f = Moo (f (Moo f))

unMoo :: Moo f -> f (Moo f)
unMoo (Moo u) = u

Empty場合はfromMooどうですか?2つのケースの唯一の違いは、このEmptyケースでは、のabsurd代わりにがあるということです\moo -> fromMoo moo alg。すべてのVoid -> a関数はabsurdなので、別のEmptyケースは必要ありません。

fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo (Moo u) = \alg -> alg (fmap (\moo -> fromMoo moo alg) u)

考えられる表面的な微調整は、fromMoo引数を反転することです。そのため、引数をfmapラムダとして書き込む必要はありません。

foldMoo :: Functor f => (f a -> a) -> Moo f -> a
foldMoo alg (Moo u) = alg (fmap (foldMoo alg) u)

または、よりポイントフリー:

foldMoo :: Functor f => (f a -> a) -> Moo f -> a
foldMoo alg = alg . fmap (foldMoo alg) . unMoo

この時点で、定義をもう一度見てみると、名前の変更が適切であることを示しています。

newtype Fix f = Fix (f (Fix f))

unfix :: Fix f -> f (Fix f)
unfix (Fix u) = u

cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg = alg . fmap (cata alg) . unfix

fromFix :: Functor f => Fix f -> forall a. (f a -> a) -> a
fromFix t = \alg -> cata alg t

そしてそれがあります:すべてのforall a. (f a -> a) -> a関数は\alg -> cata alg tいくつかのためのフォームを持っていt :: Fix fます。したがって、fixToMu全射であり、望ましい同型性があります。

補遺

コメントでは、cata Fix t = t導出における帰納法の適用性について緊密な質問が出されました。少なくとも、ファンクタの法則とパラメトリック性は、fmap (cata Fix)余分な作業を作成しないことを保証します(たとえば、構造を拡大したり、掘り下げる追加の再帰的な位置を導入したりしない)。誘導の誘導ステップで問題になります。そのため、t有限構造の場合、f (Fix t)最終的には空の基本ケースに到達し、すべてが明確になります。私たちが許可した場合t、無限であることを、しかし、我々は、際限なく下降維持することができますfmap後にfmapした後fmap、これまで基本ケースに到達することなく、。

ただし、無限構造の状況は、最初に思われるほどひどいものではありません。そもそも無限構造を実行可能にするものである怠惰により、無限構造を遅延して消費することができます。

GHCi> :info ListF
data ListF a b = Nil | Cons a b
    -- etc.
GHCi> ones = Fix (Cons 1 ones)
GHCi> (\(Fix (Cons a _)) -> a) (cata Fix ones)
1
GHCi> (\(Fix (Cons _ (Fix (Cons a _)))) -> a) (cata Fix ones)
1

再帰的な位置の連続は無限に広がりますが、任意の時点で停止して、周囲のListF関数コンテキストから有用な結果を得ることができます。このようなコンテキストは繰り返し発生し、の影響を受けないfmapため、使用する可能性のある構造の有限セグメントはの影響を受けませんcata Fix

この怠惰猶予は、この議論の他の箇所で述べた方法を反映し、怠惰は、固定点間の区別を崩壊MuFixおよびNu。怠惰なしでFixは、生産的な共起をエンコードするのに十分ではないためNu、最大の固定小数点であるに切り替える必要があります。ここに違いの小さなデモがあります:

GHCi> :set -XBangPatterns
GHCi> -- Like ListF, but strict in the recursive position.
GHCi> data SListF a b = SNil | SCons a !b deriving Functor
GHCi> ones = Nu (\() -> SCons 1 ()) ()
GHCi> (\(Nu c a) -> (\(SCons a _) -> a) (c a)) ones
1
GHCi> ones' = Fix (SCons 1 ones')
GHCi> (\(Fix (SCons a _)) -> a) ones'
^CInterrupted.

どのように正当化しcata Fix t = tますか?Fix fの初期代数を仮定するとf、少しショートカットのように見えます。(関連する回答からリンクされた証明は、パラメトリック性を両方の方法で使用することでこれを回避するようです。)
Li-yao Xia

fixToMuの全容性の証明がわかりません。「forall a。(fa-> a)->最初から関数を定義したい場合」それは私たちが望んでいることではありません。代わりに、いくつかについてk :: forall a. (f a -> a) -> aそれを示す必要があります。k = \alg -> cata alg tt
Li-yao Xia

[1/2] @ Li-yaoXia(1)cata Fixでは、がありcata Fix = Fix . fmap (cata Fix) . unfixます。tが再帰的な位置fmap (cata Fix)を持たない場合は、何もしませんcata Fix t = Fix (unfix t) = t。それが再帰的な位置を持っている場合、すべてfmap (cata Fix)cata Fixそれらに適用するだけです、それは帰納によって問題を解決するのに十分に見えます。
duplode

[2/2] @ Li-yaoXia(2)全射率について:引数はk、代数(f Void値が必要な場合)を直接適用するか、またはを使用fmapして再帰的に適用することによって取得できる必要があります。\ alg-> cata alg t`形式で表現されます。だから私はあなたが提案したことをしたと思いますが、「一から」はそれを説明する言葉の最良の選択ではなかったかもしれません。
duplode

1
@ Li-yaoXia私は全射率の議論を説明する言語を微調整し、cata Fix t = t導出を追加しました(実際、2つの議論の類似性を考えると、レイアウトすることは答えの2番目の部分の基礎を準備するのに役立つと思います) 。改善点を強調していただきありがとうございます。
二重化
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.