Мы выходим на финишную прямую.
 Осталось несколько методов класса-построителя, с которыми надо разобраться, и вы готовы к самостоятельному плаванию!
Вот эти методы:
- Whileдля повторения.
- TryWithи- TryFinallyдля обработки исключений.
- Useдля управления освобождаемыми ресурсами.
Помните, что, как и в предыдущих случаях, не все методы требуют реализации.
 Если While вам не нужен, можете о нём не беспокоиться.
Одно важное замечание, прежде чем мы начнём: все обсуждаемые здесь методы, основаны на использовании отложенных функций.
 Если вы не используете отложенные функции, ни один из этих методов не даст ожидаемых результатов.
Обратите внимание, что «построитель» в контексте вычислительных выражений — это не то же самое, что объектно-ориентированный паттерн «строитель», который используется для конструирования и валидации объектов.
Реализуем While
Все мы знаем, что означает `while`` в обычном коде, но что он означает в контексте вычислительных выражений?
 Чтобы разобраться, нам потребуется вернуться к концепции продолжений.
В предыдущих постах мы узнали, что последовательность выражений можно превратить в цепочку продолжений, например, так:
Bind(1,fun x ->
   Bind(2,fun y ->
     Bind(x + y,fun z ->
        Return(z)  // или Yield
Это ключ к пониманию цикла while — его можно развернуть похожим образом.
Для начала немного терминологии.
 Цикл while состоит из двух частей:
- В начале цикла - whileесть проверка, которая вычисляется перед каждой итерацией, чтобы определить, надо ли выполнять тело цикла. Если результат вычисления равен- false, «выходим» из цикла. В вычислительных выражениях проверка известна как охранное выражение. У проверяющей функции нет параметров и она возвращает булево значение, так что её сигнатура —- unit -> bool.
- Также у цикла - whileесть тело, которое выполняется, пока проверка успешно проходит. В вычислительных выражениях это отложенная функция, которая вычисляет завёрнутое значение. Поскольку тело цикла- whileвсегда одно и то же, каждый раз вызывается одна и та же функция. Функция, реализующая тело, не имеет параметров и ничего не возвращает, поэтому её сигнатура- unit -> wrapped unit. (Она не должна ничего возвращать и в то же время должна возвращать завёрнутое значение, поэтому её результат — завёрнутое ничего — прим. переводчика).
На этом этапе, мы уже можем реализовать цикл while, опираясь на функции-продолжения. Пока вместо настоящего F# используем псевдо-код:
// вызываем тестовую функцию
let bool = guard()
if not bool
then
    // выходим из цикла
    return what??
else
    // выполняем тело цикла
    body()
    // возвращаемся к началу цикла
    // вызываем тестовую функцию снова
    let bool' = guard()
    if not bool'
    then
        // выходим из цикла
        return what??
    else
        // выполняем тело цикла снова
        body()
        // возвращаемся к началу цикла
        // вызываем тестовую функцию в третий раз
        let bool'' = guard()
        if not bool''
        then
            // выходим из цикла
            return what??
        else
            // выполняем тело цикла в третий раз
            body()
            // и т.д.
Сразу возникает вопрос: что нужно вернуть, если проверка в цикле не сработала?
 Что ж, мы встречали подобное, когда обсуждали if..then.. и ответ, естественно — использовать значение Zero.
Затем мы должны избавиться от результата body().
 Да, это функция с типом возврата unit, так что возвращать ничего не нужно, но и в этом случае мы хотим каким-то образом встроить в неё собственный код, потому что нам нужны побочные эффекты.
 И, конечно, её надо вызывать с помощью Bind.
Вот версия псевдо-кода с методами Zero и Bind:
// вызываем тестовую функцию
let bool = guard()
if not bool
then
    // выходим из цикла
    return Zero
else
    // выполняем тело цикла
    Bind( body(), fun () ->
        // вызываем тестовую функцию снова
        let bool' = guard()
        if not bool'
        then
            // выходим из цикла
            return Zero
        else
            // выполняем тело цикла снова
            Bind( body(), fun () ->
                // вызываем тестовую функцию в третий раз
                let bool'' = guard()
                if not bool''
                then
                    // выходим из цикла
                    return Zero
                else
                    // выполняем тело цикла в третий раз
                    Bind( body(), fun () ->
                    // и т.д.
В нашем случае, функция-продолжение, передаваемая в Bind, имеет параметр типа unit, поскольку функция body не возвращает значения.
В конечном итоге, мы можем упростить псевдо-код, путём сворачивания в рекурсивную функцию, как здесь:
member this.While(guard, body) =
    // вызываем тестовую функцию
    if not (guard())
    then
        // выходим из цикла
        this.Zero()
    else
        // выполняем тело цикла
        this.Bind( body(), fun () ->
            // вызываем рекурсивно
            this.While(guard, body))
В действительности, это стандартная «шаблонная» реализация While почти для всех классов-построителей.
Тонкий, но важный момент заключается в том, что мы должны правильно выбрать значение Zero.
 В предыдущих постах мы видели, что можем использовать для Zero и значение None и значение Some (), в зависимости от процесса.
 Однако, чтобы While работал корректно, мы должны в качестве Zero использовать Some (), а не None, потому что передача None в Bind приведёт к преждевременному завершению цикла.
Также обратите внимание, что мы не используем ключевое слово rec, не смотря на то, что имеем дело с рекурсивной функцией.
 Оно требуется только для рекурсивных функций F#, а не для методов классов.
While: инструкция по применению
Давайте посмотрим, как цикл работает в построителе build.
 Вот класс-построитель целиком, с методом While:
type TraceBuilder() =
    member this.Bind(m, f) =
        match m with
        | None ->
            printfn "Bind с None. Выходим."
        | Some a ->
            printfn "Bind с Some(%A). Продолжаем" a
        Option.bind f m
    member this.Return(x) =
        Some x
    member this.ReturnFrom(x) =
        x
    member this.Zero() =
        printfn "Zero"
        this.Return ()
    member this.Delay(f) =
        printfn "Delay"
        f
    member this.Run(f) =
        f()
    member this.While(guard, body) =
        printfn "While: проверка"
        if not (guard())
        then
            printfn "While: Zero"
            this.Zero()
        else
            printfn "While: цикл"
            this.Bind( body(), fun () ->
                this.While(guard, body))
// создаём экземпляр процесса
let trace = new TraceBuilder()
Взглянув на сигнатуру While, мы видимо, что параметр body имеет тип unit -> unit option, то есть это отложенная функция.
 Как я писал выше, если вы должным образом не реализуете Delay, то получите неопределённое поведение и загадочные ошибки компилятора.
type TraceBuilder =
    // прочие методы
    member
      While : guard:(unit -> bool) * body:(unit -> unit option) -> unit option
Вот простой цикл, использующий мутабельную переменную, значение которой увеличивается на 1 при каждой итерации.
let mutable i = 1
let test() = i < 5
let inc() = i <- i + 1
let m = trace {
    while test() do
        printfn "i = %i" i
        inc()
    }
Обработка исключений с помощью try..with
Обработка исключений реализуется похожим образом.
Исследуя выражение try..with, мы видим, что оно состоит из двух частей:
- У него есть тело - try, которое выполняется один раз. В вычислительных выражениях оно превратится в отложенную функцию, которая возвращает завёрнутое значение. У функции нет параметров, так что её сигнатура — это- unit -> wrapped type.
- Часть - withобрабатываем исключения. В качестве параметра она принимает исключение и возвращает тот же тип, что и часть- try, так что её сигнатура — это- exception -> wrapped type.
Мы можем создать псевдо-код для обработчика исключений, с учётом этих данных:
try
    let wrapped = delayedBody()
    wrapped  // возвращаем завёрнутое значение
with
| e -> handlerPart e
И это в точности соответствует стандартной реализации:
member this.TryWith(body, handler) =
    try
        printfn "TryWith Тело"
        this.ReturnFrom(body())
    with
        e ->
            printfn "TryWith Обработка исключения"
            handler e
Как видите, общей практикой для возврата завёрнутого значения является вызов ReturnFrom, так что оно будет обработано также, как и другие завёрнутые значения.
Вот фрагмент примера, чтобы разобраться, как действует обработчик:
trace {
    try
        failwith "бах!"
    with
    | e -> printfn "Исключение! %s" e.Message
    } |> printfn "Результат %A"
Реализуем try..finally
Конструкция try..finally очень похожа на try..with.
- У него есть тело - try, которое выполняется однократно. Тело не имеет параметров и его сигнатура — это- unit -> wrapped type.
- Часть - finallyвызывается всегда. У неё нет параметров и она возвращает- unit, так что её сигнатура — это- unit -> unit.
Как и в случае с try..with, стандартная реализация очевидна.
member this.TryFinally(body, compensation) =
    try
        printfn "TryFinally Цикл"
        this.ReturnFrom(body())
    finally
        printfn "TryFinally восстановление"
        compensation()
Ещё один фрагментик:
trace {
    try
        failwith "бах!"
    finally
        printfn "ок"
    } |> printfn "Результат %A"
Реализуем using
Последний метод для реализации — это Using.
 Это метод построителя для реализации ключевого слова use!.
Вот что документация MSDN говорит об use!:
{| use! value = expr in cexpr |}
транслируется в:
builder.Bind(expr, (fun value -> builder.Using(value, (fun value -> {| cexpr |} ))))
Иными словами, ключевое слово use! запускает как Bind, так и Using.
 Сначала Bind распаковывает завёрнутое значение, и затем незавёрнутый освобождаемый объект передаётся в Using, для последующего освобождения, вместе с функцией-продолжением в качестве второго параметра.
Это реализуется довольно просто.
 Как и в других методах, у нас есть тело, или часть-продолжение выражения Using, которое выполняется один раз.
 У этой функции есть параметр disposable, так что её сигнатура — это #IDisposable -> wrapped type.
Конечно мы хотим быть уверены, что освобождаемое значение освобождается в любом случае, так что нам надо завернуть вызов функции-тела в TryFinally.
Вот стандартная реализация:
member this.Using(disposable:#System.IDisposable, body) =
    let body' = fun () -> body disposable
    this.TryFinally(body', fun () ->
        match disposable with
            | null -> ()
            | disp -> disp.Dispose())
Замечания:
- Параметр для - TryFinally— это- unit -> wrapped, с- unitв качестве первого параметра, так что мы создаём отложенную функцию- body', и передаём именно её.
- Освобождаемое значение — это класс, так что он может быть - null, и мы должны отдельно обрабатывать этот случай. В противном случае мы просто освобождаем его в продолжении- finally.
Вот демонстрация Using в действии.
 Обратите внимание, что makeResource создаёт завёрнутый освобождаемый объект.
 Если он не заворачивается, нам не нужна специальная версия use! и мы можем использовать нормальный оператор use.
let makeResource name =
    Some {
    new System.IDisposable with
    member this.Dispose() = printfn "Освобождаем %s" name
    }
trace {
    use! x = makeResource "привет"
    printfn "Освобождаем в use!"
    return 1
    } |> printfn "Результат: %A"
Пересмотрим работу For
Напоследок вернёмся к реализации оператора For.
 В предыдущих примерах For принимал простой параметр-список.
 Но, имея в запасе Using и While, мы можем переписать его так, чтобы он принимал любую реализацию IEnumerable<_> или seq.
Вот стандартная реализация для For:
member this.For(sequence:seq<_>, body) =
       this.Using(sequence.GetEnumerator(),fun enum ->
            this.While(enum.MoveNext,
                this.Delay(fun () -> body enum.Current)))
Как видите, этот код отличается от предыдущих реализаций обработкой обобщённого параметра IEnumerable<_>.
- Мы явно перебираем элементы коллекции, используя свойства и методы интерфейса - IEnumerator<_>.
- IEnumerator<_>реализует- IDisposable, так что мы заворачиваем итератор в- Using,
- Мы используем - While .. MoveNextдля итерации.
- Далее, мы передаём - enum.Currentв функцию-тело.
- Наконец, мы откладываем вызов функции-тела, используя - Delay.
Полный код без трассировки
До сих пор наш код был сложнее, чем надо, из-за операторов трассировки и печати.
 Трассировка полезна для понимания происходящего, но она убивает простоту методов.
Так что в качестве финального шага бросим взгляд на полный код класса-построителя для trace, но на этот раз без всякого постороннего кода.
 Несмотря на то, что код достаточно сложный, назначение и реализация каждого метода должны быть вам понятны.
type TraceBuilder() =
    member this.Bind(m, f) =
        Option.bind f m
    member this.Return(x) = Some x
    member this.ReturnFrom(x) = x
    member this.Yield(x) = Some x
    member this.YieldFrom(x) = x
    member this.Zero() = this.Return ()
    member this.Delay(f) = f
    member this.Run(f) = f()
    member this.While(guard, body) =
        if not (guard())
        then this.Zero()
        else this.Bind( body(), fun () ->
            this.While(guard, body))
    member this.TryWith(body, handler) =
        try this.ReturnFrom(body())
        with e -> handler e
    member this.TryFinally(body, compensation) =
        try this.ReturnFrom(body())
        finally compensation()
    member this.Using(disposable:#System.IDisposable, body) =
        let body' = fun () -> body disposable
        this.TryFinally(body', fun () ->
            match disposable with
                | null -> ()
                | disp -> disp.Dispose())
    member this.For(sequence:seq<_>, body) =
        this.Using(sequence.GetEnumerator(),fun enum ->
            this.While(enum.MoveNext,
                this.Delay(fun () -> body enum.Current)))
После всех наших обсуждений, теперь код кажется совсем крошечным.
 И всё же этот построитель реализует все стандартные методы, включая отложенные функции.
 Бездна функциональности всего в нескольких строках!
 
          