Почему поведение Lazy.CreateFromValue изменилось в F # между версиями .NET Framework?

Я только что столкнулся с изменением поведения между версиями каркаса при компиляции этого фрагмента кода в F #:

let test = Lazy.CreateFromValue 1

Скомпилированный против .NET framework 2.0, выражение приводит к "уже созданному" Lazy объекту, то есть:

test.IsValueCreated = true

При компиляции с .NET framework 4.0 выражение приводит к "неоцененному" ленивому объекту, то есть:

 test.IsValueCreated = false

Только после доступа к test.Value в последнем случае эти два эквивалента.

Я нигде не мог найти ссылки на это изменение, поэтому мой вопрос в том, почему это ведет себя по-другому и какова была причина изменения (это нарушение). На мой взгляд, поведение в .NET 2.0 имеет больше смысла - создание Lazy-объекта из конкретного значения должно привести к "уже оцененному" ленивому объекту.

Ответ 1

До .NET 4.0 инфраструктура не поставлялась с Lazy<'T>.

Эта древняя история, но F # первоначально имела свою собственную реализацию Lazy, отличную от того, что у нас есть сегодня - вы можете увидеть ее здесь.

Эта реализация была оставлена, когда стало ясно, что Lazy подходит к самой структуре. Вместо этого F # 2.0 поставляется с собственной реализацией System.Lazy<'T> (обратите внимание на пространство имен) в сборке FSharp.Core. В этой реализации вы все еще можете увидеть здесь. Идея заключалась в том, что, как только .NET 4.0 будет доступен, F # будет без проблем собирать System.Lazy<'T> вместо этого, не нарушая пользователей. Это работало по большей части, но это было не совсем без проблем.

Вы заметите, что реализация F # имеет член CreateFromValue, который дает значение типа Lazy<'T>, уже отмеченное как оцененное. Это имеет смысл в семантическом смысле, поскольку вы, очевидно, даете ему оцененное значение в первую очередь.

Почему тогда реализация .NET 4.0 не делает то же самое?

Если вы посмотрите documentation для Lazy<'T>, вы обнаружите, что нет способа создать его в оцениваемом состоянии. На нем нет элемента CreateFromValue, и никакой конструктор не принимает значение 'T, а только Func<'T>. CreateFromValue фактически предоставляется как метод расширения с помощью F #.

Было бы довольно легко обеспечить этот метод неразрывным способом:

static member CreateFromValue(value : 'T) : System.Lazy<'T> =
    let x = System.Lazy<'T>.Create(fun () -> value)
    x.Value |> ignore
    x

но по какой-то причине этого не произошло. Возможно, это был преднамеренный выбор - я полагаю, вы могли бы спорить и против таких изменений, и, возможно, это был надзор. Если вы посмотрите на свернутую историю этого типа, я думаю, вы согласитесь, что это могло быть хуже.

Ответ 2

Класс Lazy<T> был добавлен в фреймворк .NET 4. Поддержка F # для лени - скорее заглушка, позволяющая API для работы с .NET 2, но при использовании этого API не дает поистине ленивой реализации.

Компилятор F # создал собственный ленивый тип для .NET 2, поскольку он предшествовал версии .NET 4. В .NET 2 ленивый тип просто задает значение явно и не очень ленив (см. источник) при использовании CreateFromValue.

Из-за этого расширение CreateFromValue на .NET 4 действительно лениво - оно создает ленивый тип с функцией, которая возвращает Значение. В .NET 2 вместо этого используется , который выполняет API Lazy<'T>.CreateFromValue, но на самом деле не ленив.