web-dev-qa-db-ja.com

リストに特化した組織型、接合型、および未来型

私はそれを理解することになりました。私が行った講演のビデオとスライドをご覧ください。

元の質問:

一般的な再帰スキーム(つまり、Fixを使用する再帰スキーム)を理解するための努力の中で、さまざまなスキームのリストのみのバージョンを記述することが有用であることがわかりました。これにより、実際のスキームをより簡単に理解できます(Fixの追加オーバーヘッドなし)。

ただし、zygoおよびfutuのリストのみのバージョンを定義する方法はまだわかりません。

これまでの私の専門的な定義は次のとおりです。

cataL :: (a ->        b -> b) -> b -> [a] -> b
cataL f b (a : as) = f a    (cataL f b as)
cataL _ b []       = b

paraL :: (a -> [a] -> b -> b) -> b -> [a] -> b
paraL f b (a : as) = f a as (paraL f b as)
paraL _ b []       = b

-- TODO: histo

-- DONE: zygo (see below)

anaL  :: (b ->       (a, b))               -> b -> [a]
anaL  f b = let (a, b') = f b in a : anaL f b'

anaL' :: (b -> Maybe (a, b))               -> b -> [a]
anaL' f b = case f b of
    Just (a, b') -> a : anaL' f b'
    Nothing      -> []

apoL :: ([b] -> Maybe (a, Either [b] [a])) -> [b] -> [a]
apoL f b = case f b of
    Nothing -> []
    Just (x, Left c)  -> x : apoL f c
    Just (x, Right e) -> x : e

-- DONE: futu (see below)

hyloL  :: (a -> c -> c) -> c -> (b -> Maybe (a, b)) -> b -> c
hyloL f z g = cataL f z . anaL' g

hyloL' :: (a -> c -> c) -> c -> (c -> Maybe (a, c))      -> c
hyloL' f z g = case g z of
    Nothing     -> z
    Just (x,z') -> f x (hyloL' f z' g)

リストのhistozygo、およびfutuはどのように定義しますか?

38
haroldcarr

Zygomorphismは、2つのsemi-mutually再帰関数から構築されたフォールドに付ける、高確率の数学的な名前です。例を挙げましょう。

pm :: [Int] -> Int+が交互に数字のリストを介して散らばるpm [v,w,x,y,z] = v - (w + (x - (y + z)))などの関数-plus-minusの場合)を想像してください。プリミティブな再帰を使用して書き出すことができます。

lengthEven :: [a] -> Bool
lengthEven = even . length

pm0 [] = 0
pm0 (x:xs) = if lengthEven xs
             then x - pm0 xs
             else x + pm0 xs

明らかにpm0compositional ではありません-各位置でリスト全体の長さを調べて、加算するか減算するかを決定する必要があります。 Paramorphismは、フォールディング関数がフォールドの各反復でサブツリー全体をトラバースする必要がある場合、この種のプリミティブな再帰をモデル化します。そのため、少なくとも、確立されたパターンに適合するようにコードを書き換えることができます。

paraL :: (a -> [a] -> b -> b) -> b -> [a] -> b
paraL f z [] = z
paraL f z (x:xs) = f x xs (paraL f z xs)

pm1 = paraL (\x xs acc -> if lengthEven xs then x - acc else x + acc) 0

しかし、これは非効率的です。 lengthEvenは、パラモーフィズムの各反復でリスト全体を走査し、O(n2)アルゴリズム。


lengthEvenparaの両方がfoldr...でcatamorphismとして表現できることに注意することで前進できます。

cataL = foldr

lengthEven' = cataL (\_ p -> not p) True
paraL' f z = snd . cataL (\x (xs, acc) -> (x:xs, f x xs acc)) ([], z)

...これは、2つの操作をリスト上の1つのパスに融合できることを示唆しています。

pm2 = snd . cataL (\x (isEven, total) -> (not isEven, if isEven
                                                      then x - total
                                                      else x + total)) (True, 0)

別のフォールドの結果に依存するフォールドがあり、それらを1つのリストのトラバーサルに融合することができました。接合型は、まさにこのパターンをキャプチャします。

zygoL :: (a -> b -> b) ->  -- a folding function
         (a -> b -> c -> c) ->  -- a folding function which depends on the result of the other fold
         b -> c ->  -- zeroes for the two folds
         [a] -> c
zygoL f g z e = snd . cataL (\x (p, q) -> (f x p, g x p q)) (z, e)

フォールドの各反復で、fはカタモフィズムのように最後の反復からの回答を見ますが、gは両方の関数の回答を見ます。 gfと絡まります。

最初の折りたたみ関数を使用してリストの長さが偶数か奇数かをカウントし、2番目のリストを使用して合計を計算することにより、pmをザイモモルフィズムとして記述します。

pm3 = zygoL (\_ p -> not p) (\x isEven total -> if isEven
                                                then x - total
                                                else x + total) True 0

これは、古典的な関数型プログラミングスタイルです。リストを消費するという重いリフティングを行う高階関数があります。ロジックをプラグインして結果を集計するだけでした。構成は明らかに終了します(foldrの終了を証明するだけで済みます)。また、ブートするのに元の手書きバージョンよりも効率的です。

Aside:@AlexRは、zygomorphismにはmutumorphismと呼ばれる姉がいるとコメントで指摘しています。栄光。 mutuzygoを一般化します両方折り畳み関数は、前の反復からの他方の結果を検査できます。

mutuL :: (a -> b -> c -> b) ->
         (a -> b -> c -> c) ->
         b -> c ->
         [a] -> c
mutuL f g z e = snd . cataL (\x (p, q) -> (f x p q, g x p q)) (z, e)

余分な引数を無視するだけで、zygoからmutuを回復できます。 zygoL f = mutuL (\x p q -> f x p)


もちろん、これらの折り畳みパターンはすべて、リストから任意のファンクターの不動点に一般化されます。

newtype Fix f = Fix { unFix :: f (Fix f) }

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

para :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
para f = snd . cata (\x -> (Fix $ fmap fst x, f x))

zygo :: Functor f => (f b -> b) -> (f (b, a) -> a) -> Fix f -> a
zygo f g = snd . cata (\x -> (f $ fmap fst x, g x))

mutu :: Functor f => (f (b, a) -> b) -> (f (b, a) -> a) -> Fix f -> a
mutu f g = snd . cata (\x -> (f x, g x))

zygoの定義とzygoLの定義を比較します。また、zygo Fix = para、および後の3つのフォールドはcataの観点から実装できることに注意してください。フォルドロジーでは、すべてが他のすべてに関連しています。

汎用バージョンからリストバージョンを回復できます。

data ListF a r = Nil_ | Cons_ a r deriving Functor
type List a = Fix (ListF a)

zygoL' :: (a -> b -> b) -> (a -> b -> c -> c) -> b -> c -> List a -> c
zygoL' f g z e = zygo k l
    where k Nil_ = z
          k (Cons_ x y) = f x y
          l Nil_ = e
          l (Cons_ x (y, z)) = g x y z

pm4 = zygoL' (\_ p -> not p) (\x isEven total -> if isEven
                                                 then x - total
                                                 else x + total) True 0
39

Histomorphismモデルdynamic programming、以前のサブ計算の結果を集計する手法。 ( 値の帰納法 と呼ばれることもあります。)組織型では、フォールディング関数はフォールドの以前の反復の結果のテーブルにアクセスできます。これを、畳み込み関数が最後の反復の結果のみを見ることができるカタモフィズムと比較してください。組織型には後知恵があります-すべての歴史を見ることができます。

これがアイデアです。入力リストを使用すると、フォールディング代数はbsのシーケンスを出力します。 histoは、各bが出現するたびに書き留めて、結果のテーブルに添付します。履歴内のアイテムの数は、処理したリストレイヤーの数に等しくなります。リスト全体を破棄するまでに、操作の履歴の長さはリストの長さと等しくなります。

これは、リスト(ory)の繰り返しの履歴のようです:

data History a b = Ancient b | Age a b (History a b)

Historyは、事物と結果のペアのリストであり、最後に[]- thingに対応する追加の結果があります。入力リストの各レイヤーを対応する結果とペアにします。

cataL = foldr

history :: (a -> History a b -> b) -> b -> [a] -> History a b
history f z = cataL (\x h -> Age x (f x h) h) (Ancient z)

リスト全体を右から左に折り畳むと、最終結果はスタックの一番上になります。

headH :: History a b -> b
headH (Ancient x) = x
headH (Age _ x _) = x

histoL :: (a -> History a b -> b) -> b -> [a] -> b
histoL f z = headH . history f z

History acomonad ですが、headH(néeextract)はhistoLを定義するために必要なすべてです。)


Historyは、入力リストの各レイヤーに対応する結果をラベル付けします。 cofree comonadは、任意の構造の各層にラベルを付けるパターンをキャプチャします。

data Cofree f a = Cofree { headC :: a, tailC :: f (Cofree f a) }

HistoryListFに接続して単純化することで、Cofreeを思いつきました。)

これをfreeモナドと比較し、

data Free f a = Free (f (Free f a))
              | Return a

Freeは連産品タイプです。 Cofreeは製品タイプです。 Freefsのラザニアを重ね、ラザニアの下部に値aを付けます。 Cofreeは、各層でaの値でラザニアを重ねます。無料のモナドは、一般化された外部ラベル付きツリーです。 cofree comonadsは、一般化された内部ラベル付きツリーです。

Cofreeを手にすると、リストから任意のファンクターの不動点に一般化できます。

newtype Fix f = Fix { unFix :: f (Fix f) }

cata :: Functor f => (f b -> b) -> Fix f -> b
cata f = f . fmap (cata f) . unFix

histo :: Functor f => (f (Cofree f b) -> b) -> Fix f -> b
histo f = headC . cata (\x -> Cofree (f x) x)

そしてもう一度リストのバージョンを回復します。

data ListF a r = Nil_ | Cons_ a r deriving Functor
type List a = Fix (ListF a)
type History' a b = Cofree (ListF a) b

histoL' :: (a -> History' a b -> b) -> b -> List a -> b
histoL' f z = histo g
    where g Nil_ = z
          g (Cons_ x h) = f x h

Asidehistofutuの双対です。それらのタイプを見てください。

histo :: Functor f => (f (Cofree f a) -> a) -> (Fix f -> a)
futu  :: Functor f => (a  ->  f (Free f a)) -> (a -> Fix f)

futuは、矢印が反転し、histoFreeに置き換えられたCofreeです。組織型は過去を見る;未来型は未来を予測します。 cata f . ana g準同型に融合できるように、histo f . futu gchronomorphism

数学的な部分を省略しても、 HinzeとWuによるこのペーパー には、組織型とその使用法に関する優れた、例に基づいたチュートリアルがあります。

12

futuについて他の誰もまだ答えていないので、つまずくようにします。 _ListF a b = Base [a] = ConsF a b | NilF_を使用します

_recursion-schemes_futu :: Unfoldable t => (a -> Base t (Free (Base t) a)) -> a -> tで型を取得します。

Unfoldable制約を無視して、tを_[b]_に置き換えます。

_(a -> Base [b] (Free (Base [b]) a)) -> a -> [b]
(a -> ListF b (Free (ListF b) a)) -> a -> [b]
_

Free (ListF b) a)はリストであり、末尾にa型の穴がある場合があります。これは、_([b], Maybe a)_と同型であることを意味します。だから今、私たちは持っています:

_(a -> ListF b ([b], Maybe a)) -> a -> [b]
_

最後のListFを削除し、_ListF a b_がMaybe (a, b)と同型であることを確認します。

_(a -> Maybe (b, ([b], Maybe a))) -> a -> [b]
_

さて、型テトリスをプレイすることが唯一の賢明な実装につながると確信しています。

_futuL f x = case f x of
  Nothing -> []
  Just (y, (ys, mz)) -> y : (ys ++ fz)
    where fz = case mz of
      Nothing -> []
      Just z -> futuL f z
_

結果の関数を要約すると、futuLはシード値と少なくとも1つの結果を生成する可能性のある関数を受け取り、結果が生成された場合は新しいシード値を取得します。

最初はこれが同等だと思った

_notFutuL :: (a -> ([b], Maybe a)) -> a -> [b]
notFutuL f x = case f x of
  (ys, mx) -> ys ++ case mx of
    Nothing -> []
    Just x' -> notFutuL f x'
_

そして実際には、多かれ少なかれ、しかし、1つの重要な違いは、実際のfutuが生産性を保証することです(つまり、fが常に戻る場合、次のリスト要素)。

12
Alex R