La definición de un combinador Y en F # es
let rec y f x = f (y f) x
f espera tener como primer argumento alguna continuación para los subproblemas recursivos. Usando yf como continuación, vemos que f se aplicará a llamadas sucesivas a medida que podamos desarrollar
let y f x = f (y f) x = f (f (y f)) x = f (f (f (y f))) x etc...
El problema es que, a priori, este esquema impide el uso de cualquier optimización de llamada de cola: de hecho, podría haber alguna operación pendiente en las f, en cuyo caso no podemos simplemente mutar el marco de pila local asociado con f.
Entonces :
- por un lado, usar el combinador Y requiere una continuación explícitamente diferente de la función misma.
- Por otro lado, para aplicar el TCO, nos gustaría no tener ninguna operación pendiente en f y solo llamar a f.
¿Conoces alguna forma en que esos dos puedan reconciliarse? ¿Como una Y con truco de acumulador, o una Y con truco de CPS? ¿O un argumento que demuestre que no hay forma de hacerlo?
f#
recursion
tail-call
continuation
nicolas
fuente
fuente
f
. Podemos ver quey
podría hacerf
una llamada final con un sonido(y f)
, pero como usted dice,f
podría tener alguna operación pendiente. Creo que sería interesante saber si hay un combinador separado que sea más amigable con la cola. Me pregunto si esta pregunta recibiría mejor atención en el sitio CS Stackexchange.Respuestas:
No, y con razón, en mi humilde opinión.
El Y-combinator es una construcción teórica y solo es necesaria para completar el cálculo lambda (recuerde, no hay bucles en el cálculo lambda, ni las lambdas tienen nombres que podamos usar para la recursión).
Como tal, el combinador Y es realmente fascinante.
Pero : ¡en realidad nadie usa el combinador Y para la recursión real! (Excepto tal vez por diversión, para demostrar que realmente funciona).
La optimización de llamadas de cola, OTOH, es, como su nombre lo indica, una optimización. No agrega nada a la expresividad de un lenguaje, solo nos preocupamos por consideraciones prácticas como el espacio de pila y el rendimiento del código recursivo.
Entonces su pregunta es: ¿Hay soporte de hardware para la reducción beta? (La reducción beta es cómo se reducen las expresiones lambda, ya sabes). Pero ningún lenguaje funcional (que yo sepa) compila su código fuente a una representación de expresiones lambda que se reducirán beta en tiempo de ejecución.
fuente
No estoy completamente seguro de esta respuesta, pero es lo mejor que se me ocurrió.
El combinador y es inherentemente perezoso, en lenguajes estrictos, la pereza debe agregarse manualmente a través de lambdas adicionales.
Parece que su definición requiere pereza para terminar, o el
(y f)
argumento nunca terminaría de evaluar y tendría que evaluar si lof
usó o no . El TOC en un contexto vago es más complicado, y además el resultado de la(y f)
composición de funciones repetidas sin aplicación con elx
. No estoy seguro de que esto necesite tomar O (n) memoria donde n es la profundidad de la recursión, pero dudo que pueda lograr el mismo tipo de TOC que sea posible con algo como (cambiar a Haskell porque no sé realmente F#)Si aún no lo sabe, la diferencia entre
foldl
yfoldl'
en Haskell puede arrojar algo de luz sobre la situación.foldl
está escrito como se haría en un idioma entusiasta. Pero en lugar de ser TOC, en realidad es peor quefoldr
porque el acculador almacena una potencia potencialmente enorme que no puede evaluarse parcialmente. (Esto está relacionado con por qué tanto foldl como foldl 'no funcionan en listas infinitas.) Por lo tanto, en versiones más recientes de Haskell,foldl'
se agregó lo que obliga a evaluar el acumulador cada vez que la función se repite para garantizar que no se cree un enorme thunk. Estoy seguro de que http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27 puede explicar esto mejor que yo.fuente