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.