Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why has the behaviour of Lazy.CreateFromValue changed in F# between .NET framework versions?

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.

like image 405
GeorgS Avatar asked Dec 06 '16 17:12

GeorgS


2 Answers

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.

like image 95
scrwtp Avatar answered Oct 19 '22 08:10

scrwtp


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.

like image 35
Reed Copsey Avatar answered Oct 19 '22 07:10

Reed Copsey