I'm just beginning F# so please be kind if this is basic.
I've read that a function marked lazy is evaluated only once and then cached. For example:
let lazyFunc = lazy (1 + 1)
let theValue = Lazy.force lazyFunc
Compared to this version which would actually run each time it's called:
let eagerFunc = (1 + 1)
let theValue = eagerFunc
Based on that, should all functions be made lazy? When would you not want to? This is coming from material in the book "Beginning F#".
First of all, it may be helpful to note that none of the things you have defined is a function - eagerFunc
and theValue
are values of type int
and lazyFunc
is a value of type Lazy<int>
. Given
let lazyTwo = lazy (1 + 1)
and
let eagerTwo = 1 + 1
the expression 1 + 1
will not be evaluated more than once no matter how many times you use eagerTwo
. The difference is that 1 + 1
will be evaluated exactly once when defining eagerTwo
, but will be evaluated at most once when lazyTwo
is used (it will be evaluated the first time that the Value
property is accessed, and then cached so that further uses of Value
do not need to recalculated it). If lazyTwo
's Value
is never accessed, then its body 1 + 1
will never be evaluated.
Typically, you won't see much benefit to using lazy values in a strict language like F#. They add a small amount of overhead since accessing the Value
property requires checking whether the value has already been calculated. They might save you a bit of calculation if you have something like let lazyValue = lazy someVeryExpensiveCalculationThatMightNotBeNeeded()
, since the expensive calculation will only take place if the value is actually used. They can also make some algorithms terminate which otherwise would not, but this is not a major issue in F#. For instance:
// 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
If function executions have side-effects and it is important to see the side-effects each time the function is called (say it wraps an I/O function) you would not want it to be lazy.
There are also functions that are so trivial that executing them each time is faster than caching the value--
let eagerFunc = (1 + 1)
is a let binding, and will only execute once. let eagerFunc() = (1 + 1)
is a function accepting unit
(nothing) and returning an int
. It will execute each time it's called. In a sense, every function is lazy, that is, it only executes when called. However, the lazy
keyword (and System.Lazy
, which it returns) will execute the expression/function given to it at most once. Subsequent calls to the Value
property will return the cached result. This is useful when computation of the value is expensive.
Many functions will not be suitable for use with lazy
because they are either non-deterministic (may return a different result with each invocation) or parameterized. Of course, it's possible to use a fully-applied (a value is supplied for each parameter) version of such functions, but generally the variability is desired.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With