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 foldr
los 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), para
los 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 para
es 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 foldr
usandopara
bastante facilidad; que es un poco más difícil de definir para
a 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 para
con foldr
es 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, snd
descarta la copia de la entrada y da solo el valor de salida. No es muy eficiente, pero si te interesa la expresividad pura, para
no te da más quefoldr
. Si usa esta foldr
versión codificada de para
, luego safeTail
tomará un tiempo lineal después de todo, copiando la cola elemento por elemento.
Entonces, eso es todo: para
es una versión más conveniente de la foldr
cual 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, para
definidos cata
por 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 TreeF
donde
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 para
versión mucho más fácil, ya que en cada nodo deberá insertar un subárbol pero conservar el otro tal como estaba.
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
, me parece.