2011-03-23 10 views
22

En la notación de flecha, puede usar la palabra clave rec para escribir definiciones recursivas. Entonces, por ejemplo:¿Cómo funciona la palabra clave haskell rec?

rec 
    name <- function -< input 
    input <- otherFunction -< name 

¿Cómo puede esto alguna vez evaluar? Parece que simplemente entraría en un ciclo infinito o algo así. Sé que evalúa el combinador de flecha de bucle, pero no entiendo cómo funciona eso tampoco.

EDITAR: ese ejemplo de poderes es realmente útil. Sin embargo, ¿cómo escribirías eso con la notación de hacer? Supongo que necesitarías usar rec.

+4

[En caso] (http://www.haskell.org/haskellwiki/Keywords#rec) alguien no ha oído hablar de 'rec' antes. –

Respuesta

22

Este poco de magia funciona debido a la pereza de Haskell. Como sabrá, Haskell evalúa un valor cuando es necesario, no cuando está definido. Por lo tanto, esto funciona si no necesita el valor ingresado directamente, o tal vez más tarde.

rec se implementa utilizando la función loop de . Se define como sigue:

class Arrow a => ArrowLoop a where 
     loop :: a (b,d) (c,d) -> a b c 

instance ArrowLoop (->) where 
     loop f b = let (c,d) = f (b,d) in c 

Puede ver: La salida simplemente se retroalimenta como entrada. Se calculará solo una vez, porque Haskell solo evaluará d cuando sea necesario.

Aquí hay un ejemplo real de cómo usar el combinador loop directamente. Esta función calcula todos los poderes de que el argumento de:

powers = loop $ \(x,l) -> (l,x:map(*x)l) 

(También podrían escribir como este en su lugar: powers x = fix $ (x :) . map (*x))

¿Cómo funciona? Bueno, la lista infinita de poderes está en el argumento l. La evaluación es el siguiente:

powers = loop $ \(x,l) -> (l,x:map(*x)l) ==> 
powers b = let (c,d) = (\(x,l) -> (l,x:map(*x)l)) (b,d) in c ==> 
powers b = let (c,d) = (d,b:map(*b)d) in d ==> -- Now we apply 2 as an argument 
powers 2 = let (c,d) = (d,2:map(*2)d) in d ==> 
     = let (c,(2:d)) = (d,2:map(*2)d) in c ==> 
     = let (c,(2:4:d)) = ((2:d),2:map(*2)(2:d)) in c ==> 
     = let (c,(2:4:8:d)) = ((2:4:d),2:map(*2)(2:4:d)) in ==> -- and so on 
+0

Entiendo eso, pero ¿de dónde sale la primera muestra de salida? ¿Hay algún caso base definido en alguna parte, porque no lo he visto? Si en mi ejemplo no se proporcionan ni la entrada ni el nombre, ¿cómo puede evaluar eso alguna vez? ¿O uno de esos dos debe ser provisto? –

+1

El truco es que Haskell no evaluará los argumentos no utilizados. Es solo que el flujo de control no es necesariamente lo mismo que piensas. – fuz

+1

La 'f (b, _)' solo debe usar la entrada 'b' al calcular la salida 'd'. 'f' puede usar tanto 'b' como 'd' cuando construya 'c' siempre que no fuerce la evaluación de 'd' para hacer que se cuelgue.Puedes pensarlo de esta manera: cuando 'c' es forzado, causa que 'd' se compute de 'b' y luego 'c' para que se calcule a partir de 'b' y 'd'. –

11

Aquí es un ejemplo real ish:

loop f b = let (c,d) = f (b,d) in c 

f (b,d) = (drop (d-2) b, length b) 

main = print (loop f "Hello World") 

Este programa salidas "LD". La función 'loop f' toma una entrada 'b' y crea una salida 'c'. Lo que 'f' está haciendo es estudiar 'b' para producir 'length b' que se devuelve al loop y se enlaza con 'd'.

En 'loop', esta 'd = longitud b' se introduce en la 'f' donde se usa en el cálculo en drop.

Esto es útil para trucos como la construcción de una lista inmutable doblemente vinculada (que también puede ser circular). También es útil para atravesar 'b' una vez para producir alguna 'd' analítica (como la longitud o el elemento más grande) y para construir una nueva estructura 'c' que dependa de 'd'. La pereza evita tener que atravesar 'b' dos veces.

Cuestiones relacionadas