In the F# core library there are higher-order functions that take a thunk (fun () -> ...
), but could also conceptually take a Lazy<_>
, such as Option.defaultWith
. F# has good syntactical support for Lazy<_>
through the lazy
keyword, but I can't think of any function in the F# core library that takes Lazy<_>
instead of a thunk. I would guess this is because it's "more FP" to use thunks than the .NET-specific Lazy<_>
type.
But apart from being vaguely "less FP": If the value is needed at most 1 time, what are the practical considerations of using Lazy<_>
instead of thunks? Are there, for example, performance differences (CPU and/or allocations/memory)? Other concerns? What kind of situations are better resolved with Lazy<_>
instead of thunks?
Being a separate object which wraps a thunk, provides synchronization and holds the result reference, Lazy<_>
has some additional overhead compared to a simple thunk.
If you know you're only going to evaluate the thunk (at most) once, I don't think there's a reason why you wouldn't use a function if you can. You can wrap a call to Lazy<_>
's Value
in another function, but in this case it has no benefit that I know of.
One scenario I remember where Lazy<_>
was useful was when we had two separate feature flags in a product which both potentially required initialization of Orleankka actor system if enabled - expensive operation which should only be done once per application startup.
So our options were either a lot of nested if
s, mutable options, or this:
let actorSystem = lazy initializeActorSystem ()
if feature1Enabled then
let as = actorSystem.Value
...
if feature2Enabled then
let as = actorSystem.Value
...
Unlike a thunk, Lazy<_>
also allows you to check if the thunk was evaluated. From the same exaple:
if actorSystem.IsValueCreated then
actorSystem.Value.Dispose()
Other cases are in general when you aren't sure you would evaluate the thunk at most once, especially if it can happen concurrently. I think we also have use case like that for reading some external configuration when not provided locally to several components that are being started concurrently - Lazy<_>
ensures we make the remote call only once (if needed at all) regardless of which component first finds out it needs it.
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