2011-07-13 19 views
8

Estoy comenzando F # así que sea amable si esto es básico.F # Lazy Evaluation vs Non-Lazy

He leído que una función marcada como floja se evalúa solo una vez y luego se almacena en caché. Por ejemplo:

let lazyFunc = lazy (1 + 1) 
let theValue = Lazy.force lazyFunc 

En comparación con esta versión que realmente ejecute cada vez que se llama:

let eagerFunc = (1 + 1) 
let theValue = eagerFunc 

Sobre la base de que, en caso todas las funciones hacerse perezoso? ¿Cuándo no querrías? Esto proviene del material en el libro "Beginning F #".

+0

¿Qué versión de F # es esto? Tengo una secuencia que está actuando perezosamente, pero no fue abiertamente creada de esa manera. Estoy tratando de forzarlo a que se complete. – octopusgrabbus

Respuesta

13

En primer lugar, puede ser útil tener en cuenta que ninguna de las cosas que ha definido es una función - eagerFunc y theValue son valores de tipo int y lazyFunc es un valor de tipo Lazy<int>. Dada

let lazyTwo = lazy (1 + 1) 

y

let eagerTwo = 1 + 1 

la expresión 1 + 1 se no ser evaluados más de una vez, no importa cuántas veces se utiliza eagerTwo. La diferencia es que 1 + 1 serán evaluados exactamente una vez cuando definireagerTwo, pero serán evaluados como máximo una vez cuando lazyTwo es utiliza (se evaluará la primera vez que se accede a la propiedad Value, y luego almacenado en caché para que otros usos de Value no necesiten recalcularlo). Si lazyTwo 's Value nunca se accede, entonces su cuerpo 1 + 1 será nunca se evaluará.

Normalmente, no verá mucho beneficio al usar valores diferidos en un lenguaje estricto como F #. Agregan una pequeña cantidad de sobrecarga ya que para acceder a la propiedad Value es necesario verificar si el valor ya se ha calculado. Podrían ahorrarte un poco de cálculo si tienes algo como let lazyValue = lazy someVeryExpensiveCalculationThatMightNotBeNeeded(), ya que el cálculo costoso solo tendrá lugar si el valor se usa realmente. También pueden hacer que terminen algunos algoritmos que de otra forma no lo harían, pero este no es un problema importante en F #. Por ejemplo:

// throws an exception if x = 0.0 
let eagerDivision x = 
    let oneOverX = 1.0/x 
    if x = 0.0 then 
     printfn "Tried to divide by zero" // too late, this line is never reached 
    else 
     printfn "One over x is: %f" oneOverX 

// succeeds even if x = 0.0, since the quotient is lazily evaluated 
let lazyDivision x = 
    let oneOverX = lazy (1.0/x) 
    if x = 0.0 then 
     printfn "Tried to divide by zero" 
    else 
     printfn "One over x is: %f" oneOverX.Value 
+0

¿No utilizar '()' implica que es una función? Buenos puntos en cualquier caso. Tienes razón, mis ejemplos son súper triviales. – Yuck

+0

@Yuck - los paréntesis significan cosas diferentes en diferentes contextos. Cuando los usa como '(1 + 1)', simplemente sirven para indicar agrupamiento y precedencia. Si ha definido 'let eagerFunc() = ...', entonces los paréntesis indican que 'eagerFunc' es una función, pero tenga en cuenta que esto es diferente de lo que escribió. – kvb

6

Si las ejecuciones de funciones tienen efectos secundarios y es importante ver los efectos colaterales cada vez que se llama a la función (digamos que envuelve una función de E/S) no querría que fuera floja.

También son funciones que son tan trivial que la ejecución de ellos cada vez es más rápido que el almacenamiento en caché el value--

5

let eagerFunc = (1 + 1) es una decepción de unión, y sólo se ejecutará una vez. let eagerFunc() = (1 + 1) es una función que acepta unit (nada) y devuelve un int. Se ejecutará cada vez que se llame. En cierto sentido, cada función es floja, es decir, solo se ejecuta cuando se llama. Sin embargo, la palabra clave lazy (y System.Lazy, que devuelve) ejecutará la expresión/función que se le asigna a lo sumo una vez. Las llamadas posteriores a la propiedad Value devolverán el resultado en caché. Esto es útil cuando el cálculo del valor es costoso.

Muchas funciones no serán adecuadas para su uso con lazy porque son no deterministas (pueden devolver un resultado diferente con cada invocación) o parametrizadas. Por supuesto, es posible utilizar una versión totalmente aplicada (se proporciona un valor para cada parámetro) de tales funciones, pero generalmente se desea la variabilidad.