O que são paramorfismos?

96

Lendo este artigo clássico , estou preso em paramorfismos. Infelizmente, a seção é bem estreita e a página da Wikipedia não diz nada.

Minha tradução de Haskell é:

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

Mas eu não groco isso - não tenho nenhuma intuição para a assinatura de tipo ou o resultado desejado.

O que é um paramorfismo e quais são alguns exemplos úteis em ação?


Sim, eu vi essas perguntas , mas elas não cobrem os paramorfismos diretamente e apenas apontam para recursos que podem ser úteis como referências, mas não como materiais de aprendizagem.

Matt Fenwick
fonte
1
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs), penso eu.
Daniel Fischer
De acordo com esta página wiki , os paramorfismos "modelam a recursão primitiva sobre os tipos de dados indutivos". Isso significa alguma coisa / ajuda?
manhã
4
O artigo "Fissão" de Jeremy Gibbons, que indiquei em um comentário a uma dessas questões, é um material de aprendizagem muito útil. cs.ox.ac.uk/jeremy.gibbons/publications/fission.pdf Ele funciona por meio de vários padrões de recursão muito claramente.
stephen tetley
1
A reescrita de Daniel pode ser simplificada como para f base xs = foldr g base (init $ tails xs) where g (x:xs) = f x xs . Isso lembra o Common Lispmaplist .
Will Ness

Respostas:

110

Sim, é isso para. Compare com catamorfismo ou 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

Algumas pessoas chamam os paramorfismos de "recursão primitiva" em contraste com os catamorfismos ( foldr) sendo "iteração".

Onde foldr's dois parâmetros recebem um valor calculado recursivamente para cada subobjeto recursivo dos dados de entrada (aqui, esse é o fim da lista), paraos parâmetros' s obtêm o subobjeto original e o valor calculado recursivamente a partir dele.

Um exemplo de função que é bem expresso paraé a coleção dos elementos adequados de uma lista.

suff :: [x] -> [[x]]
suff = para (\ x xs suffxs -> xs : suffxs) []

de modo a

suff "suffix" = ["uffix", "ffix", "fix", "ix", "x", ""]

Possivelmente mais simples ainda é

safeTail :: [x] -> Maybe [x]
safeTail = para (\ _ xs _ -> Just xs) Nothing

em que o ramo "contras" ignora seu argumento calculado recursivamente e apenas retorna a cauda. Avaliado preguiçosamente, o cálculo recursivo nunca acontece e a cauda é extraída em tempo constante.

Você pode definir foldrusando com parabastante facilidade; é um pouco mais complicado para definir paraa partir foldr, mas é certamente possível, e todos devem saber como se faz!

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)

O truque para definir paracom foldré reconstruir uma cópia dos dados originais, de modo que tenhamos acesso a uma cópia da cauda em cada etapa, mesmo que não tenhamos acesso ao original. Ao final, snddescarta a cópia da entrada e dá apenas o valor de saída. Não é muito eficiente, mas se você está interessado em expressividade pura, não paraoferece mais do que foldr. Se você usar esta foldrversão codificada de para, então safeTaillevará um tempo linear, copiando a cauda elemento por elemento.

Então, é isso: paraé uma versão mais conveniente do foldrque lhe dá acesso imediato ao final da lista, bem como ao valor calculado a partir dela.

No caso geral, trabalhar com um tipo de dados gerado como o ponto de fixação recursivo de um functor

data Fix f = In (f (Fix f))

Você tem

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)

e, novamente, os dois são mutuamente definíveis, sendo paradefinidos catapelo mesmo truque de "fazer uma cópia"

para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))

Novamente, paranão é mais expressivo do que cata, mas mais conveniente se você precisar de acesso fácil às subestruturas da entrada.

Edit: Lembrei-me de outro bom exemplo.

Considere as árvores de pesquisa binárias fornecidas por Fix TreeFonde

data TreeF sub = Leaf | Node sub Integer sub

e tente definir a inserção para árvores de pesquisa binárias, primeiro como a cata, depois como a para. Você achará a paraversão muito mais fácil, já que em cada nó você precisará inserir em uma subárvore, mas preservar a outra como estava.

trabalhador de porco
fonte