Ответ 1
Да, это 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
двум параметрам дается рекурсивно вычисленное значение для каждого рекурсивного подобъекта входных данных (здесь, что хвост списка), параметры 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
займет линейное время, скопировав элемент tail по элементу.
Итак, это: 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)
и снова, эти два являются взаимно определяемыми, причем para
определяется из cata
тем же "сделать копию" трюк
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
Опять же, para
не более выразителен, чем cata
, но более удобно, если вам нужен легкий доступ к подструктурам ввода.
Изменить: Я вспомнил еще один хороший пример.
Рассмотрим двоичные деревья поиска, заданные Fix TreeF
, где
data TreeF sub = Leaf | Node sub Integer sub
и попробуйте определить вставку для двоичных деревьев поиска, сначала как cata
, а затем как para
. Вы найдете версию para
намного проще, так как в каждом node вам нужно будет вставить в одно поддерево, но сохранить другое как было.