Sì, è così para. Confronta con il catamorfismo o 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
Alcune persone chiamano i paramorfismi "ricorsione primitiva" in contrasto con catamorfismi ( foldr) che sono "iterazione".
Dove foldrai due parametri viene assegnato un valore calcolato ricorsivamente per ogni sottooggetto ricorsivo dei dati di input (qui, questa è la coda dell'elenco), parai parametri di ottengono sia il sottooggetto originale che il valore calcolato ricorsivamente da esso.
Una funzione di esempio che è ben espressa con paraè la raccolta dei suffissi appropriati di una lista.
suff :: [x] -> [[x]]
suff = para (\ x xs suffxs -> xs : suffxs) []
così che
suff "suffix" = ["uffix", "ffix", "fix", "ix", "x", ""]
Forse è ancora più semplice
safeTail :: [x] -> Maybe [x]
safeTail = para (\ _ xs _ -> Just xs) Nothing
in cui il ramo "contro" ignora il suo argomento calcolato ricorsivamente e restituisce solo la coda. Valutato pigramente, il calcolo ricorsivo non avviene mai e la coda viene estratta a tempo costante.
Puoi definire l' foldrutilizzo paraabbastanza facilmente; è un po 'più complicato da definire parada foldr, ma è certamente possibile, e tutti dovrebbero sapere come si fa!
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)
Il trucco per definire paracon foldrè ricostruire una copia dei dati originali, in modo da avere accesso a una copia della coda ad ogni passaggio, anche se non avevamo accesso all'originale. Alla fine, sndscarta la copia dell'input e fornisce solo il valore di output. Non è molto efficiente, ma se sei interessato alla pura espressività, paranon ti dà più di foldr. Se usi questa foldrversione codificata di para, dopotutto safeTailci vorrà del tempo lineare, copiando l'elemento della coda per elemento.
Quindi, questo è tutto: paraè una versione più conveniente difoldr che ti dà accesso immediato alla coda dell'elenco e al valore calcolato da esso.
Nel caso generale, lavorare con un tipo di dati generato come punto fisso ricorsivo di un funtore
data Fix f = In (f (Fix f))
hai
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 ancora, i due sono definibili reciprocamente, con paradefiniti catadallo stesso trucco "fai una copia"
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
Anche in questo caso, paranon è più espressivo di cata, ma più conveniente se è necessario un facile accesso alle sottostrutture dell'input.
Modifica: mi sono ricordato di un altro bell'esempio.
Considera gli alberi di ricerca binari dati da Fix TreeFdove
data TreeF sub = Leaf | Node sub Integer sub
e prova a definire l'inserimento per gli alberi binari di ricerca, prima come un cata, poi come un para. Troverai la paraversione molto più semplice, poiché ad ogni nodo dovrai inserire in una sottostruttura ma conservare l'altra com'era.
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs), mi sembra.