Так це 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
в якому гілка "мінуси" ігнорує свій рекурсивно обчислений аргумент і просто віддає хвіст. Оцінено ліниво, рекурсивне обчислення ніколи не відбувається, і хвіст витягується за постійний час.
Ви можете визначити 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)
і знову ж таки, ці два варіанти є взаємовизначними, і їх 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
і спробуйте визначити вставку для бінарних дерев пошуку, спочатку як a cata
, потім як a para
. Ви знайдете para
версію набагато простіше, оскільки на кожному вузлі вам потрібно буде вставити в одне піддерево, але зберегти інше таким, яким воно було.
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
, міркує.