Что такое параморфизмы?

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 Он очень четко работает с многочисленными шаблонами рекурсии.
Стивен Тетли
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

в котором ветвь cons игнорирует свой рекурсивно вычисляемый аргумент и просто возвращает хвост. При ленивой оценке рекурсивное вычисление никогда не происходит, а хвост извлекается за постоянное время.

Вы можете довольно легко определить foldrusing 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версию намного проще, поскольку на каждом узле вам нужно будет вставить одно поддерево, но сохранить другое, как было.

свинарник
источник