Бложим как фрики
2024 年 9 月 2 日Шлепа — Большой Русский Бенчмарк
2024 年 9 月 2 日網頁設計
Мы выходим на финишную прямую。
Осталось несколько методов класса-построителя, с которыми надо разобратцс анирыми надо разобратцс аниыми надо разобратцс аниы!
內容:
-
While
для повторения。 -
TryWith
иTryFinally
для обработки исключений。 -
Use
для управления освобождаемыми ресурсами。
再次,請注意,請注意,如果您有任何疑問,請與我們聯繫。
埃斯利 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
, опираясь на функции-продолжения。
// вызываем тестовую функциюlet bool=guard()if not boolthen // выходим из цикла 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 boolthen // выходим из цикла return Zeroelse // выполняем тело цикла 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#, ане для методов класов.
同時: инструкция по применению
Давайте посмотрим, как цикл работает в построителе 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
Вот простой цикл, использующий мутабельную переменнннную, значени переменнннну
let mutable i=1let test()=i
Обработка исключений с помощью 嘗試..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..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
。
Это метод построителя для реализации ключевого слова 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
принимал простой параметр-список。
Но, имея в заpace 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)))
После всех наших обсуждений, теперь код кажется совсем крошечным。
И всё же этот построитель реализует все стандартные методы, включаяоилные методы, включаяоил
Бездна функциональности всего в нескольких строках!