Cosa sono i paramorfismi?

Leggendo questo classico documento , sono bloccato sui paramorfismi. Sfortunatamente la sezione è piuttosto sottile e la pagina di Wikipedia non dice nulla.

La mia traduzione di Haskell è:

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

Ma non lo bevo – non ho intuito per la firma del tipo o il risultato desiderato.

Cos’è un paramorfismo e quali sono alcuni esempi utili in azione?


Sì, ho visto queste domande , ma non trattano direttamente i paramorfismi e indicano solo risorse che possono essere utili come riferimenti, ma non come materiali di apprendimento.

Sì, questo è para . Confronta con il catamorfismo, o foldr :

 para :: (a -> [a] -> b -> b) -> b -> [a] -> b foldr :: (a -> b -> b) -> b -> [a] -> b para cn (x : xs) = cx xs (para cn xs) foldr cn (x : xs) = cx (foldr cn xs) para cn [] = n foldr cn [] = n 

Alcuni chiamano i paramorfismi “ricorsione primitiva” per contrasto con i catamorfismi (foldr) “iterazione”.

Laddove a due parametri di foldr viene assegnato un valore calcolato ricorsivamente per ogni suboggetti ricorsivo dei dati di input (qui, questa è la coda della lista), i parametri di para ottengono sia il subobject originale che il valore calcolato in modo ricorsivo 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 ricorsivamente calcolato e restituisce la coda. Valutato pigramente, il calcolo ricorsivo non avviene mai e la coda viene estratta in tempo costante.

Puoi definire foldr usando para abbastanza facilmente; è un po ‘più complicato definire para da foldr , ma è certamente ansible, e tutti dovrebbero sapere come è fatto!

 foldr cn = para (\ x xs t -> cxt) n para cn = snd . foldr (\ x (xs, t) -> (x : xs, cx xs t)) ([], n) 

Il trucco per definire para con foldr è ribuild una copia dei dati originali, in modo da ottenere l’accesso a una copia della coda ad ogni passaggio, anche se non abbiamo avuto 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 ti dà solo foldr . Se si utilizza questa versione di para safeTail in safeTail , safeTail prenderà del tempo lineare dopo tutto, copiando l’elemento tail per elemento.

Quindi, questo è tutto: para è una versione più comoda di foldr che ti dà accesso immediato alla coda della lista così come il valore calcolato da essa.

Nel caso generale, funziona con un tipo di dati generato come fixpoint ricorsivo di un functor

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

hai

 cata :: Functor f => (ft -> 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 mutuamente definibili, con para definito da cata dallo stesso trucco “make a copy”

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

Di nuovo, para non è più espressivo del cata , ma è più conveniente se hai bisogno di accedere facilmente alle sottostrutture dell’input.

Modifica: ho ricordato un altro bel esempio.

Considera gli alberi di ricerca binaria forniti da Fix TreeF dove

 data TreeF sub = Leaf | Node sub Integer sub 

e prova a definire l’inserimento per gli alberi di ricerca binari, prima come cata , poi come para . Troverai la versione para molto più semplice, poiché in ogni nodo dovrai inserire in una sottostruttura, ma conservare l’altra com’era.