¿Qué son los paramorfismos?

96

Al leer este artículo clásico , estoy atrapado en paramorfismos. Desafortunadamente, la sección es bastante delgada y la página de Wikipedia no dice nada.

Mi traducción de Haskell es:

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

Pero no asimilo eso, no tengo ninguna intuición para la firma tipográfica o el resultado deseado.

¿Qué es un paramorfismo y cuáles son algunos ejemplos útiles en acción?


Sí, he visto estas preguntas , pero no cubren los paramorfismos directamente y solo apuntan a recursos que pueden ser útiles como referencias, pero no como materiales de aprendizaje.

Matt Fenwick
fuente
1
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs), me parece.
Daniel Fischer
Según esta página wiki , los paramorfismos "modelan la recursividad primitiva sobre los tipos de datos inductivos". ¿Eso significa algo / ayuda?
huon
4
El artículo "Fisión" de Jeremy Gibbons al que señalé en un comentario a una de esas preguntas es un material de aprendizaje muy útil. cs.ox.ac.uk/jeremy.gibbons/publications/fission.pdf Funciona a través de numerosos patrones de recursividad con mucha claridad.
Stephen Tetley
1
La reescritura de Daniel se puede simplificar como para f base xs = foldr g base (init $ tails xs) where g (x:xs) = f x xs . Esto recuerda a Common Lispmaplist .
Will Ness

Respuestas:

110

Sí, eso es para. Compare con 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

Algunas personas llaman a los paramorfismos "recursividad primitiva" en contraste con los catamorfismos ( foldr) que son "iteración".

Donde foldrlos dos parámetros reciben un valor calculado de forma recursiva para cada subobjeto recursivo de los datos de entrada (aquí, esa es la cola de la lista), paralos parámetros obtienen tanto el subobjeto original como el valor calculado de forma recursiva a partir de él.

Una función de ejemplo que se expresa muy bien con paraes la colección de los suficientes suficientes de una lista.

suff :: [x] -> [[x]]
suff = para (\ x xs suffxs -> xs : suffxs) []

así que eso

suff "suffix" = ["uffix", "ffix", "fix", "ix", "x", ""]

Posiblemente más simple aún es

safeTail :: [x] -> Maybe [x]
safeTail = para (\ _ xs _ -> Just xs) Nothing

en el que la rama "contras" ignora su argumento calculado de forma recursiva y simplemente devuelve la cola. Evaluado con pereza, el cálculo recursivo nunca ocurre y la cola se extrae en tiempo constante.

Puede definir foldrusandopara bastante facilidad; que es un poco más difícil de definir paraa partir foldr, pero es ciertamente posible, y todo el mundo debe saber cómo se hace!

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)

El truco para definir paracon foldres reconstruir una copia de los datos originales, de modo que obtengamos acceso a una copia de la cola en cada paso, aunque no tuviéramos acceso al original. Al final, snddescarta la copia de la entrada y da solo el valor de salida. No es muy eficiente, pero si te interesa la expresividad pura, parano te da más quefoldr . Si usa esta foldrversión codificada de para, luego safeTailtomará un tiempo lineal después de todo, copiando la cola elemento por elemento.

Entonces, eso es todo: paraes una versión más conveniente de la foldrcual le brinda acceso inmediato al final de la lista, así como al valor calculado a partir de ella.

En el caso general, trabajar con un tipo de datos generado como el punto fijo recursivo de un funtor

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

tienes

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)

y de nuevo, los dos son mutuamente definibles, paradefinidos catapor el mismo truco "hacer una copia"

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

De nuevo, para no es más expresivo cata, pero es más conveniente si necesita un fácil acceso a las subestructuras de la entrada.

Editar: Recordé otro buen ejemplo.

Considere los árboles de búsqueda binarios dados por Fix TreeFdonde

data TreeF sub = Leaf | Node sub Integer sub

e intente definir la inserción para árboles de búsqueda binarios, primero como a cata, luego como a para. Encontrará la paraversión mucho más fácil, ya que en cada nodo deberá insertar un subárbol pero conservar el otro tal como estaba.

trabajador porcino
fuente