Ответ 1
На самом деле существуют два разных способа реализации продолжения строителей в F #. Один из них состоит в представлении задержанных вычислений с использованием монадического типа (если он поддерживает некоторый способ представления отложенных вычислений, например, Async<'T>
или unit -> option<'T>
, как показано kkm.
Однако вы также можете использовать гибкость выражений вычисления F # и использовать другой тип в качестве возвращаемого значения Delay
. Затем вам нужно соответствующим образом изменить операцию Combine
, а также реализовать член Run
, но все это работает довольно хорошо:
type OptionBuilder() =
member x.Bind(v, f) = Option.bind f v
member x.Return(v) = Some v
member x.Zero() = Some ()
member x.Combine(v, f:unit -> _) = Option.bind f v
member x.Delay(f : unit -> 'T) = f
member x.Run(f) = f()
member x.While(cond, f) =
if cond() then x.Bind(f(), fun _ -> x.While(cond, f))
else x.Zero()
let maybe = OptionBuilder()
Хитрость заключается в том, что компилятор F # использует Delay
, когда у вас есть вычисление, которое должно быть отложено - это: 1), чтобы обернуть весь расчет, 2) когда вы последовательно составляете вычисления, например. используя if
внутри вычисления и 3), чтобы задержать тела while
или for
.
В приведенном выше определении член Delay
возвращает unit -> M<'a>
вместо M<'a>
, но это отлично, потому что Combine
и while
принимают unit -> M<'a>
как свой второй аргумент. Более того, добавив Run
, который оценивает функцию, вычисляется результат блока maybe { .. }
(функция с задержкой), потому что весь блок передается в Run
:
// As usual, the type of 'res' is 'Option<int>'
let res = maybe {
// The whole body is passed to `Delay` and then to `Run`
let! a = Some 3
let b = ref 0
while !b < 10 do
let! n = Some () // This body will be delayed & passed to While
incr b
if a = 3 then printfn "got 3"
else printfn "got something else"
// Code following `if` is delayed and passed to Combine
return a }
Это способ определения построителя вычислений для несрочных типов, который, скорее всего, более эффективен, чем тип упаковки внутри функции (как в kkm-решении), и не требует определения специальной версии с задержкой.
Обратите внимание, что эта проблема не происходит, например. Haskell, потому что это ленивый язык, поэтому ему не нужно явно откладывать вычисления. Я считаю, что перевод F # довольно элегантен, поскольку он позволяет обрабатывать оба типа, которые задерживаются (используя Delay
, который возвращает M<'a>
), и типы, которые представляют собой только немедленный результат (используя Delay
, который возвращает функцию и Run
).