Cosa sono i paramorfismi?


96

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

La mia traduzione Haskell è:

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

Ma non me ne accorgo - non ho alcuna intuizione per la firma del tipo o il risultato desiderato.

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


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


1
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs), mi sembra.
Daniel Fischer

Secondo questa pagina wiki , i parametri "modellano la ricorsione primitiva su tipi di dati induttivi". Significa qualcosa / aiuto?
huon

4
L'articolo "Fission" di Jeremy Gibbons che ho indicato in un commento a una di queste domande è un materiale didattico molto utile. cs.ox.ac.uk/jeremy.gibbons/publications/fission.pdf Funziona molto chiaramente attraverso numerosi schemi di ricorsione.
stephen tetley

1
La riscrittura di Daniel può essere semplificata come para f base xs = foldr g base (init $ tails xs) where g (x:xs) = f x xs . Questo ricorda Common Lisp'smaplist .
Will Ness

Risposte:


110

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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.