はい、そうpara
です。カタモルフィズムと比較、またはfoldr
:
para :: (a -> [a] -> b -> b) -> b -> [a] -> b
foldr :: (a -> b -> b) -> b -> [a] -> b
para c n (x : xs) = c x xs (para c n xs)
foldr c n (x : xs) = c x (foldr c n xs)
para c n [] = n
foldr c n [] = n
準同型写像(foldr
)が「反復」であることとは対照的に、準同型写像を「原始再帰」と呼ぶ人もいます。
どこfoldr
の2つのパラメータは(リストの末尾だここで、)入力データの各再帰サブオブジェクトのために再帰的に計算された値が与えられている、para
のパラメータは、元のサブオブジェクトとそれから再帰的に計算された値の両方を取得します。
上手く表現される関数の例para
は、リストの適切な十分性のコレクションです。
suff :: [x] -> [[x]]
suff = para (\ x xs suffxs -> xs : suffxs) []
そのため
suff "suffix" = ["uffix", "ffix", "fix", "ix", "x", ""]
おそらくもっと簡単です
safeTail :: [x] -> Maybe [x]
safeTail = para (\ _ xs _ -> Just xs) Nothing
「cons」ブランチは、再帰的に計算された引数を無視し、末尾を返すだけです。遅延評価では、再帰的な計算は行われず、テールは一定の時間で抽出されます。
foldr
を使用してpara
非常に簡単に定義できます。それは定義する少しトリッキーだpara
からfoldr
、それは確かに可能だし、誰もがどのように行うのを知っている必要があります!
foldr c n = para (\ x xs t -> c x t) n
para c n = snd . foldr (\ x (xs, t) -> (x : xs, c x xs t)) ([], n)
定義するトリックpara
では、foldr
再構築することであるコピー、我々は元にはアクセスできなかったにも関わらず、各ステップでの尾のコピーへのアクセスを得るようにすることを、元のデータのを。最後snd
に、入力のコピーを破棄し、出力値のみを提供します。あまり効率的ではありませんが、純粋な表現力に興味がある場合はにすぎpara
ませんfoldr
。このでfoldr
エンコードされたバージョンのを使用するとpara
、safeTail
結局線形の時間がかかり、末尾の要素が要素ごとにコピーされます。
つまり、それだけです。リストの末尾や、リストから計算された値にすぐにアクセスできるpara
、より便利なバージョンですfoldr
。
一般的なケースでは、ファンクターの再帰的な固定点として生成されたデータ型を操作する
data Fix f = In (f (Fix f))
あなたが持っている
cata :: Functor f => (f t -> t) -> Fix f -> t
para :: Functor f => (f (Fix f, t) -> t) -> Fix f -> t
cata phi (In ff) = phi (fmap (cata phi) ff)
para psi (In ff) = psi (fmap keepCopy ff) where
keepCopy x = (x, para psi x)
繰り返しになりますが、この2つは相互に定義可能で、同じ「コピーを作成する」トリックによってpara
定義されcata
ています。
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
繰り返しますが、para
は表現力に劣るものではありませんcata
が、入力のサブ構造に簡単にアクセスする必要がある場合には便利です。
編集:私は別の良い例を思い出しました。
Fix TreeF
where によって与えられる二分探索木を考えます
data TreeF sub = Leaf | Node sub Integer sub
バイナリ検索ツリーの挿入を最初にcata
、次にとして定義してみてくださいpara
。para
各ノードで1つのサブツリーに挿入する必要がありますが、他のノードをそのまま維持するため、バージョンがはるかに簡単になります。
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
、メチンク。