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), para
os 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 foldr
usando com para
bastante facilidade; é um pouco mais complicado para definir para
a 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 para
com 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, snd
descarta 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 para
oferece mais do que foldr
. Se você usar esta foldr
versão codificada de para
, então safeTail
levará um tempo linear, copiando a cauda elemento por elemento.
Então, é isso: para
é uma versão mais conveniente do foldr
que 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 para
definidos cata
pelo mesmo truque de "fazer uma cópia"
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
Novamente, para
nã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 TreeF
onde
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 para
versão muito mais fácil, já que em cada nó você precisará inserir em uma subárvore, mas preservar a outra como estava.
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
, penso eu.