Estaba un poco confundido por la documentación de fix(aunque creo que entiendo lo que se supone que debe hacer ahora), así que miré el código fuente. Eso me dejó más confundido:
fix :: (a -> a) -> a
fix f = let x = f x in x
¿Cómo devuelve esto exactamente un punto fijo?
Decidí probarlo en la línea de comando:
Prelude Data.Function> fix id
...
Y cuelga ahí. Ahora, para ser justos, esto está en mi viejo macbook, que es un poco lento. Sin embargo, esta función no puede ser demasiado costosa desde el punto de vista computacional, ya que cualquier cosa que se pase a id devuelve lo mismo (sin mencionar que no consume tiempo de CPU). ¿Qué estoy haciendo mal?
haskell
fixpoint-combinators
letrec
Jason Baker
fuente
fuente

fix errorghci y sentirse bien consigo mismo".fixcomofix f = f (fix f). Breve, simple, funciona e idéntica a la definición matemática.fix (1:) !! (10^8). El original lo hace en memoria constante, el tuyo toma memoria lineal (lo que también lo hace un poco más lento). Es decir, usar let "ata un nudo más fuerte" y permite que se genere una estructura de datos circular, mientras que la suya no.fix! me ayudó a entenderfixmucho.Respuestas:
No estás haciendo nada malo.
fix ides un bucle infinito.Cuando decimos que
fixdevuelve el punto mínimo fijo de una función, lo queremos decir en el sentido de la teoría del dominio . Asífix (\x -> 2*x-1)es que no va a volver1, ya que si bien1es un punto de esa función fija, no es la menos uno en el dominio de pedidos.No puedo describir el orden de los dominios en uno o dos párrafos, así que los referiré al enlace de la teoría de dominios anterior. Es un tutorial excelente, fácil de leer y bastante esclarecedor. Lo recomiendo altamente.
Para la vista desde 10,000 pies,
fixes una función de orden superior que codifica la idea de recursividad . Si tienes la expresión:let x = 1:x in xLo que da como resultado la lista infinita
[1,1..], podría decir lo mismo usandofix:fix (\x -> 1:x)(O simplemente
fix (1:)), que dice que me encuentre un punto fijo de la(1:)función, IOW un valorxtal quex = 1:x... tal como definimos anteriormente. Como puede ver en la definición,fixno es más que esta idea: recursividad encapsulada en una función.También es un concepto verdaderamente general de recursividad: puede escribir cualquier función recursiva de esta manera, incluidas las funciones que utilizan la recursividad polimórfica . Entonces, por ejemplo, la función típica de fibonacci:
fib n = if n < 2 then n else fib (n-1) + fib (n-2)Se puede escribir de
fixesta manera:fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))Ejercicio: expanda la definición de
fixpara mostrar que estas dos definiciones defibson equivalentes.Pero para una comprensión completa, lea sobre la teoría de dominios. Es algo realmente genial.
fuente
fix id:fixtoma una función de tipoa -> ay devuelve un valor de tipoa. Comoides polimórfico para cualquieraa,fix idtendrá el tipoa, es decir, cualquier valor posible. En Haskell, el único valor que puede ser de cualquier tipo es bottom, ⊥, y es indistinguible de un cálculo no final. Entoncesfix idproduce exactamente lo que debería, el valor mínimo. Un peligrofixes que si ⊥ es un punto fijo de su función, entonces, por definición, es el punto menos fijo, porfixlo tanto , no terminará.undefinedtambién es un valor de cualquier tipo. Se puede definirfixcomo:fix f = foldr (\_ -> f) undefined (repeat undefined)._Y f = f (_Y f).No pretendo entender esto en absoluto, pero si esto ayuda a alguien ... entonces yippee.
Considere la definición de
fix.fix f = let x = f x in x. La parte alucinante es quexse define comof x. Pero piénselo por un minuto.x = f xDado que x = fx, entonces podemos sustituir el valor de
xen el lado derecho de eso, ¿verdad? Asi que, por lo tanto...x = f . f $ x -- or x = f (f x) x = f . f . f $ x -- or x = f (f (f x)) x = f . f . f . f . f . f . f . f . f . f . f $ x -- etc.Entonces, el truco es, para terminar,
ftiene que generar algún tipo de estructura, de modo que unfpatrón posterior pueda coincidir con esa estructura y terminar la recursividad, sin preocuparse realmente por el "valor" completo de su parámetro (?)A menos que, por supuesto, quieras hacer algo como crear una lista infinita, como lo ilustró luqui.
La explicación factorial de TomMD es buena. La firma de tipo de Fix es
(a -> a) -> a. La firma de tipo de(\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)es(b -> b) -> b -> b, en otras palabras,(b -> b) -> (b -> b). Entonces podemos decir esoa = (b -> b). De esa forma, la corrección toma nuestra función, que esa -> a, o realmente,(b -> b) -> (b -> b)y devolverá un resultado de tipoa, en otras palabrasb -> b, en otras palabras, ¡otra función!Espera, pensé que se suponía que devolvería un punto fijo ... no una función. Bueno, lo hace, algo así (ya que las funciones son datos). Puedes imaginar que nos dio la función definitiva para encontrar un factorial. Le dimos una función que no sabía cómo recurrir (por lo tanto, uno de los parámetros es una función que se usa para recurrir) y le
fixenseñamos cómo recurrir.¿Recuerdas cómo dije que
ftiene que generar algún tipo de estructura para que unfpatrón posterior pueda coincidir y terminar? Bueno, eso no es exactamente correcto, supongo. TomMD ilustró cómo podemos expandirnosxpara aplicar la función y avanzar hacia el caso base. Para su función, usó un if / then, y eso es lo que provoca la terminación. Después de repetidos reemplazos, lainparte de la definición total defixeventualmente deja de definirse en términos dexy es entonces cuando es computable y completa.fuente
Necesita una forma de que termine el punto fijo. Ampliando su ejemplo, es obvio que no terminará:
fix id --> let x = id x in x --> id x --> id (id x) --> id (id (id x)) --> ...Aquí hay un ejemplo real de mí usando fix (tenga en cuenta que no uso fix a menudo y probablemente estaba cansado / no preocupado por el código legible cuando escribí esto):
(fix (\f h -> if (pred h) then f (mutate h) else h)) q¡WTF, dices! Bueno, sí, pero hay algunos puntos realmente útiles aquí. En primer lugar, su primer
fixargumento debería ser normalmente una función que es el caso 'recurse' y el segundo argumento son los datos sobre los que actuar. Aquí está el mismo código que una función nombrada:getQ h | pred h = getQ (mutate h) | otherwise = hSi todavía está confundido, quizás el factorial sea un ejemplo más fácil:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 5 -->* 120Note la evaluación:
fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 3 --> let x = (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x in x 3 --> let x = ... in (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 3 --> let x = ... in (\d -> if d > 0 then d * (x (d-1)) else 1) 3Oh, ¿acabas de ver eso? Eso se
xconvirtió en una función dentro de nuestrathensucursal.let x = ... in if 3 > 0 then 3 * (x (3 - 1)) else 1) --> let x = ... in 3 * x 2 --> let x = ... in 3 * (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 2 -->En lo anterior, debe recordar
x = f x, por lo tanto, los dos argumentos dex 2al final en lugar de solo2.let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->¡Y me detendré aquí!
fuente
fixtiene sentido para mí. Mi respuesta depende en gran medida de lo que ya ha dicho.id xsolo se reduce ax(que luego se reduce de nuevo aid x). - Luego, en la segunda muestra (fact), cuando elxprocesador se fuerza por primera vez, el valor resultante se recuerda y se reutiliza. El nuevo cálculo de(\recurse ...) xocurriría con la definición de no compartiry g = g (y g), no con esta definición de compartirfix. - Realicé la edición de prueba aquí . Puede usarla o podría hacer la edición si lo aprueba.fix idse reduce,let x = id x in xtambién fuerza el valor de la aplicaciónid xdentro delletmarco (thunk), por lo que se reduce alet x = x in x, y este bucle. Lo parece.fixe Y es muy clara e importante en Haskell. No veo de qué sirve mostrar el orden de reducción incorrecto cuando el correcto es aún más corto, mucho más claro y más fácil de seguir, y refleja correctamente lo que realmente está sucediendo.Según tengo entendido, encuentra un valor para la función, de modo que genera lo mismo que le das. El problema es que siempre elegirá indefinido (o un bucle infinito, en haskell, los bucles indefinidos e infinitos son lo mismo) o lo que tenga más indefinidos. Por ejemplo, con id,
λ <*Main Data.Function>: id undefined *** Exception: Prelude.undefinedComo puede ver, undefined es un punto fijo, así que
fixlo elegiremos. Si en cambio lo hace (\ x-> 1: x).λ <*Main Data.Function>: undefined *** Exception: Prelude.undefined λ <*Main Data.Function>: (\x->1:x) undefined [1*** Exception: Prelude.undefinedAsí
fixque no puedo elegir indefinido. Para hacerlo un poco más conectado a bucles infinitos.λ <*Main Data.Function>: let y=y in y ^CInterrupted. λ <*Main Data.Function>: (\x->1:x) (let y=y in y) [1^CInterrupted.Nuevamente, una pequeña diferencia. Entonces, ¿qué es el punto fijo? Probemos
repeat 1.λ <*Main Data.Function>: repeat 1 [1,1,1,1,1,1, and so on λ <*Main Data.Function>: (\x->1:x) $ repeat 1 [1,1,1,1,1,1, and so on¡Es lo mismo! Dado que este es el único punto fijo,
fixdebe asentarse en él. Lo sientofix, no hay bucles infinitos o indefinidos para ti.fuente