F # Lazy Evaluation против Non-Lazy - PullRequest
10 голосов
/ 13 июля 2011

Я только начинаю F #, поэтому будьте добры, если это просто.

Я читал, что функция, помеченная как lazy, вычисляется только один раз, а затем кэшируется.Например:

let lazyFunc = lazy (1 + 1)
let theValue = Lazy.force lazyFunc

По сравнению с этой версией, которая будет запускаться при каждом вызове:

let eagerFunc = (1 + 1)
let theValue = eagerFunc

Исходя из этого, все функции должны быть отложены?Когда бы вы не захотели?Это происходит из материала в книге «Начало F #» .

Ответы [ 3 ]

15 голосов
/ 13 июля 2011

Прежде всего, может быть полезно отметить, что ни одна из вещей, которые вы определили, не является функцией - eagerFunc и theValue являются значениями типа int, а lazyFunc - значениями типа Lazy<int>. Учитывая

let lazyTwo = lazy (1 + 1)

и

let eagerTwo = 1 + 1

выражение 1 + 1 будет не оцениваться более одного раза, независимо от того, сколько раз вы используете eagerTwo. Разница в том, что 1 + 1 будет оцениваться точно один раз, когда определяет eagerTwo, но будет оцениваться не более один раз, когда lazyTwo равно used (он будет оцениваться при первом обращении к свойству Value, а затем кэшироваться, так что при дальнейшем использовании Value его не нужно будет пересчитывать). Если lazyTwo Value никогда не доступен, то его тело 1 + 1 не будет никогда не будет оценено.

Как правило, вы не увидите большой пользы от использования ленивых значений в строгом языке, таком как F #. Они добавляют небольшие накладные расходы, так как доступ к свойству Value требует проверки того, было ли это значение уже рассчитано. Они могут сэкономить вам немного вычислений, если у вас есть что-то вроде let lazyValue = lazy someVeryExpensiveCalculationThatMightNotBeNeeded(), поскольку дорогостоящий расчет будет иметь место, только если значение действительно используется. Они также могут заставить некоторые алгоритмы завершаться, что иначе не произойдет, но это не главная проблема в F #. Например:

// 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
6 голосов
/ 13 июля 2011

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

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

5 голосов
/ 13 июля 2011

let eagerFunc = (1 + 1) является привязкой let и будет выполняться только один раз. let eagerFunc() = (1 + 1) - это функция, принимающая unit (ничего) и возвращающая int. Он будет выполняться при каждом вызове. В каком-то смысле каждая функция ленива, то есть она выполняется только при вызове. Однако ключевое слово lazySystem.Lazy, которое оно возвращает) выполнит данное ему выражение / функцию не более одного раза. Последующие вызовы свойства Value вернут кэшированный результат. Это полезно, когда вычисление значения стоит дорого.

Многие функции не подойдут для использования с lazy, поскольку они либо недетерминированы (могут возвращать разные результаты при каждом вызове), либо параметризуются. Конечно, можно использовать полностью примененную (для каждого параметра значение) версию таких функций, но, как правило, требуется изменчивость.

...