Що таке параморфізми?


96

Читаючи цю класичну статтю , я застряг на параморфізмах. На жаль, розділ досить тонкий, і на сторінці Вікіпедії нічого не сказано.

Мій переклад Haskell:

para :: (a -> [a] -> b -> b) -> b -> [a] -> b
para f base = h
  where
    h []       =   base
    h (x:xs)   =   f x xs (h xs)

Але я це не мачу - я не маю ніякої інтуїції щодо підпису типу чи бажаного результату.

Що таке параморфізм і які корисні приклади в дії?


Так, я бачив ці запитання , але вони не висвітлюють безпосередньо параморфізмів і вказують лише на ресурси, які можуть бути корисними як посилання, але не як навчальні матеріали.


1
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs), міркує.
Даніель Фішер,

Відповідно до цієї вікі-сторінки , параморфізми "моделюють примітивну рекурсію щодо індуктивних типів даних". Це щось означає / допомагає?
huon

4
Стаття Джеремі Гіббонса "Ділення", на яку я вказав у коментарі до одного з цих питань, є дуже корисним навчальним матеріалом. cs.ox.ac.uk/jeremy.gibbons/publications/fission.pdf Це дуже чітко працює за допомогою численних шаблонів рекурсії.
Stephen tetley

1
Переписування Даніеля можна спростити як para f base xs = foldr g base (init $ tails xs) where g (x:xs) = f x xs . Це нагадує Common Lispmaplist .
Will Ness

Відповіді:


110

Так це 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версію набагато простіше, оскільки на кожному вузлі вам потрібно буде вставити в одне піддерево, але зберегти інше таким, яким воно було.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.