Factorial como foldN

Factorial como foldN

de Enrique Mathias Vazquez Riveiro -
Número de respuestas: 6

Hola. 

Se me complica para escribir el factorial como foldN

Empecé con el recursivo, pero no puedo escribir la función h.


factorial' :: Nat -> Nat

factorial' Zero     = Succ Zero

factorial' (Succ n) = prod (Succ n) (factorial' n)


prod :: Nat -> Nat -> Nat

prod Zero n      = Zero

prod (Succ n) m  = suma (Succ n) (prod n m )



En respuesta a Enrique Mathias Vazquez Riveiro

Re: Factorial como foldN

de Juan Pablo García Garland -

Hola! Fijate que en el práctico dice que hay que definirla "en función de foldN". En lo posible está bien que traten de hacerla *como* foldN, pero también es válido implementarla *usando* foldN.

El problema en este caso es similar a lo que pasaba cuando querían implementar "tail" usando el fold de listas (foldr).

En general, al usar los fold, solo tienen la información de las llamadas recursivas, pero no pueden acceder al argumento que están consumiendo (en particular en fac lo necesitamos, para multiplicarlo por la llamada recursiva). El truco es tratar de reconstruir ese argumento en la llamada recursiva (lo que implica que la función va a tener que retornar más de lo que le pedimos, y por eso hay que posprocesar el resultado que al final queremos, y por lo tanto vamos a *usar* el fold).

Tratá de encarar el problema con esta idea, y si no sale, seguimos este hilo.

En respuesta a Juan Pablo García Garland

Re: Factorial como foldN

de Juan Pablo García Garland -
Un detalle que ahora veo, es que la función (prod) como la tenés definida no da el producto! Está bien definirla en función de la suma y de (prod n m), pero del lado derecho va otra cosa!
En respuesta a Juan Pablo García Garland

Re: Factorial como foldN

de Enrique Mathias Vazquez Riveiro -
Buenas, sería eso: prod (Succ n) m = suma (m) (prod n m )

Entiendo la idea, pero no sé, ¿tendría que trabajar con una lista?
En respuesta a Enrique Mathias Vazquez Riveiro

Re: Factorial como foldN

de Juan Pablo García Garland -
Vamos con el ejemplo de tail (un poco diferente a Prelude.tail, en nuestro caso tail [] == [], lo que la hace similar al predecesor porque lo que hace es "quitar" una vez el constructor recursivo, cuando hay):
 
tail [] = []
tail (x:xs) = xs
 
Ahora, para escribirla como foldr tendríamos, en principio, algo así:
 
tail xs = foldr f [] xs
 
es decir, en el caso base devolvimos [], y ahora nos falta decir quién es f... El tipo de f es (a -> [a] -> [a]), y podemos escribirla con la forma (f = \h r -> _). Es decir, f captura el caso recursivo, toma la cabeza de la lista (h), el resultado de la llamada recursiva (r) y retorna algo que nos falta completar. El problema es... ¿qué ponemos ahí? Si la lista que estamos consumiendo en un paso de la recursión tiene la forma (x:xs) tenemos  que h es x, y que r debería ser tail xs. El problema es que lo que querriamos poner es xs, y no la tenemos ni la podemos reconstruir... (en particular con (f = \h r -> r), tail queda constante y con (f = \h r -> h:r), queda la identidad...)

El truco entonces es reconstruir el xs en la llamada recursiva para que pueda extraerlo a partir de "r". Construimos tail' que retorna dos cosas: el resultado de la función que queremos construir al final (tail), y ese argumento que nos falta. Algo así (esta vez me adelanto a escribirla y la explico luego):
 
tail' xs = foldr (\h (r,xs) -> (xs,h:xs)) ([],[]) xs
 
Nuestra "f" espera como llamada recursiva un par (r,xs), donde r es la cola de xs, y xs la sublista que antes queríamos y no teníamos (la cola de (h:xs), ¡que es la lista que estamos consumiendo al aplicar f!). Entonces, pasamos a devolver xs como resultado (y mantenemos la lista en el segundo miembro del par). Notar que el segundo miembro del par siempre retorna la lista original, mientras que el primero retorna la cola (entonces, en el caso recursivo, el primer miembro del par tiene la cola de la lista que estamos consumiendo...). Pensando en eso es fácil ver que el valor ([],[]) va como caso base.
 
Finalmente:
 
tail = fst . tail'
 
(por eso usamos foldr, más no implementamos como foldr).
 
Este "truco" se puede aplicar a otros tipos de datos recursivos, en particular lo podés aplicar a Nat para construir el predecesor de forma muy similar a tail.
La idea es siempre retornar un par, en donde en una componente tenemos el resultado de la función que queremos implementar, y en el otro vamos reconstruyendo el valor original, para tener siempre en la llamada recursiva el argumento recursivo del constructor que estamos consumiendo.
 
¿Y el factorial? Bueno, la misma idea. Vos necesitás el argumento n, no solo el resultado de la llamada recursiva. Aplicando la técnica, lo vas a poder tener a mano.
En respuesta a Juan Pablo García Garland

Re: Factorial como foldN

de Juan Pablo García Garland -
Agrego algo más sobre esto.
 
El fold (los fold, cada uno para su correspondiente tipo de dato) capturan la recursión estructural.Al consumir un constructor, usamos sus valores contenidos no recursivos, y las llamadas recursivas en las posiciones recursivas. En particular no tenemos acceso al valor que estamos consumiendo (a menos que hagamos magia, como antes).

Por supuesto que hay otras formas de hacer recursión, y podemos capturar esas formas con una función de alto orden. Por ejemplo:

list_rec :: (a -> [a] -> b -> b) -> b -> [a] -> b
list_rec f e [] = e
list_rec f e (x:xs) = f x xs (list_rec f e xs)
 
list_rec captura la recursión primitiva sobre las listas (¿recuerdan el ERP en el curso de lógica?). La diferencia con foldr es justamente que en list_rec tenemos acceso al argumento recursivo xs. Entonces podríamos implementar, por ejemplo tail de forma muy simple:
 
tail = list_rec (\_ xs _ -> xs) []
En respuesta a Juan Pablo García Garland

Re: Factorial como foldN

de Enrique Mathias Vazquez Riveiro -
Muchas gracias por la explicación. Muy claro.
Acá va un intento. No sé si es válido porque la g es casi el factorial y el foldN me da N.
Saludos.

g :: Nat -> (Nat,Nat)
g Zero = (Zero, Succ Zero)
g (Succ n) = ( n' , prod (Succ n) n')
where (_,n') = g n


factorial3 :: Nat -> Nat
factorial3 n = snd (g (foldN Succ Zero n))