Да вот 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
using 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)
и, опять же, эти два понятия являются взаимно определяемыми, что 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
версию намного проще, поскольку на каждом узле вам нужно будет вставить одно поддерево, но сохранить другое, как было.
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
, мне кажется.