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 foldr
ai due parametri viene assegnato un valore calcolato ricorsivamente per ogni sottooggetto ricorsivo dei dati di input (qui, questa è la coda dell'elenco), para
i 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' foldr
utilizzo para
abbastanza facilmente; è un po 'più complicato da definire para
da 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 para
con 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, snd
scarta la copia dell'input e fornisce solo il valore di output. Non è molto efficiente, ma se sei interessato alla pura espressività, para
non ti dà più di foldr
. Se usi questa foldr
versione codificata di para
, dopotutto safeTail
ci 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 para
definiti cata
dallo stesso trucco "fai una copia"
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
Anche in questo caso, para
non è 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 TreeF
dove
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 para
versione 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.