No, ese no es el combinador Y; solo estás a mitad de camino allí. Aún necesita factorizar la autoaplicación dentro de las funciones de "semilla" a las que lo está aplicando. Es decir, en lugar de esto:
Func<dynamic, Func<dynamic, dynamic>> fact =
self => n => n == 0 ? 1 : n * self(self)(n - 1);
usted debe tener presente:
Func<dynamic, Func<dynamic, dynamic>> fact =
self => n => n == 0 ? 1 : n * self(n - 1);
Nota la sola aparición de self
en la segunda definición a diferencia de las dos apariciones en la primera definición.
(editado para agregar :) Por cierto, ya que el uso de C# simula el cálculo lambda con la evaluación de llamada por valor, el combinador de punto fijo que desea utilizar es the one often called Z, not Y
(editado de nuevo para elaborar :) El ecuación que describe Y
es esto (ver the wikipedia page para la derivación):
Y g = g (Y g)
Pero en los lenguajes de programación más prácticas, a evaluar el argumento de una función antes de llamar a la función. En la comunidad de lenguajes de programación, esto se llama evaluación llamada por valor (que no debe confundirse con la forma en que los programadores C/C++/Fortran/etc distinguen "llamada por valor" versus "llamada por referencia" versus "llamada por copia-restauración" , etc.)
Pero si lo hicimos, que tendríamos
Y g = g (Y g) = g (g (Y g)) = g (g (g (Y g))) = ...
Es decir, que teníamos que pasar todo nuestro tiempo construcción de la función recursiva y nunca moverse a la aplicación de ella.
En la evaluación de llamada por nombre, por el contrario, aplica una función, aquí g
, a la expresión de argumento no evaluado, aquí (Y g)
. Pero si g
es como fact
, está esperando otro argumento antes de que haga algo. Así que esperaríamos el segundo argumento al g
antes de tratar de evaluar (Y g)
más --- y dependiendo de lo que haga la función (es decir, si tiene un caso base), es posible que no necesitemos evaluar (Y g)
en absoluto. Es por eso que Y
funciona para la evaluación llamada por nombre.
La solución para llamar por valor es cambiar la ecuación.En lugar de Y g = g (Y g)
, queremos algo parecido a la siguiente ecuación en su lugar:.
Z g = g (λx. (Z g) x)
(creo que tengo la ecuación de la derecha, o cerca de la derecha se puede calcular hacia fuera y ver si se ajusta a la definición de Z
.)
Una forma de pensar en esto es que en lugar de calcular "toda la función recursiva" y entregándola a g
, le asignamos una función que calculará la función recursiva un poco a la vez --- y solo cuando en realidad necesitamos un poco más para poder aplicarlo a un argumento (x
).
Ouch, mi cerebro. Supongo que * lo * solicité ... – Benjol
¿Hay alguna posibilidad de que pueda explicar su BTW? Está un poco por encima de mi cabeza (pero la mayor parte de esto es ...) – Benjol
Gracias por la elaboración adicional: estaba confundido por la interpretación de llamada por valor. Acabo de llegar tan lejos como tu ejemplo recursivo ('Z = f => f (f (f (f)));' funcionará para tantos 'f' como yo incluyo ...), ahora estoy trabajando en ir el siguiente paso ... – Benjol