I just encountered a change in behaviour between framework versions when compiling this piece of code in F#:
let test = Lazy.CreateFromValue 1
Compiled against .NET framework 2.0, the expression results in an "already created" Lazy object, that is:
test.IsValueCreated = true
When compiled against .NET framework 4.0, the expression results in an "unevaluated" lazy object, that is:
test.IsValueCreated = false
Only after accessing test.Value in the latter case the two are equivalent.
I couldn't find any reference to this change anywhere, hence my question is why does this behave differently and what was the reasoning behind the change (it's breaking). In my opinion, the behaviour in .NET 2.0 makes more sense - creating a Lazy object from a concrete value should result in an "already evaluated" lazy object.
Before .NET 4.0, the framework didn't ship with Lazy<'T>
.
That's ancient history, but F# originally had its own implementation of Lazy
, distinct from what we have today - you can catch a glimpse of it here.
That implementation was abandoned when it became clear that Lazy
is coming to the framework proper. Instead, F# 2.0 shipped with its own implementation of System.Lazy<'T>
(note the namespace) in FSharp.Core
assembly. That's the implementation you can still see here. The idea was that once .NET 4.0 is available, F# will seamlessly pick up System.Lazy<'T>
from there instead, without breaking the users. Which worked for the most part, but it wasn't entirely without problems.
You'll note that F# implementation has a CreateFromValue
member that yields a value of type Lazy<'T>
already marked as evaluated. Which makes perfect sense semantically, since you're obviously giving it an evaluated value in the first place.
Why is it then that .NET 4.0 implementation doesn't do the same?
If you look at the documentation for Lazy<'T>
, you'll find that there's no way of creating it in an evaluated state. There's no CreateFromValue
member on it, and no constructor takes a value of 'T
, only a Func<'T>
. CreateFromValue
is actually provided as an extension method by F#.
It would be quite easy to provide this method in a non-breaking way:
static member CreateFromValue(value : 'T) : System.Lazy<'T> =
let x = System.Lazy<'T>.Create(fun () -> value)
x.Value |> ignore
x
but that didn't happen for some reason. Perhaps it was a deliberate choice - I suppose you could argue both for and against such change - but perhaps it was an oversight. If you look at the convoluted history of this type, I think you'll agree that it could have been worse.
The Lazy<T>
class was added to the framework in .NET 4. The F# support for lazy is more of a stub to allow the API to work on .NET 2, but doesn't provide a truly lazy implementation when using this API.
The F# compiler created it's own lazy type for .NET 2, as it predated the .NET 4 version. In .NET 2, the lazy type just sets the value explicitly, and isn't truly lazy (see the source) when using CreateFromValue
.
Because of this, the CreateFromValue
extension on .NET 4 is truly lazy - it instantiates the lazy type with a function that returns the value. On .NET 2, the internal type is used instead, which fulfills the API of Lazy<'T>.CreateFromValue
, but isn't truly lazy.
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