以下是我迄今为止所拥有的:
type Maybe<'a> = option<'a>
let succeed x = Some(x)
let fail = None
let bind rest p =
match p with
| None -> fail
| Some r -> rest r
let rec whileLoop cond body =
if cond() then
match body() with
| Some() ->
whileLoop cond body
| None ->
fail
else
succeed()
let forLoop (xs : 'T seq) f =
using (xs.GetEnumerator()) (fun it ->
whileLoop
(fun () -> it.MoveNext())
(fun () -> it.Current |> f)
)
whileLoop
可以很好地支持for
循环,但我不知道如何获得while循环的支持。部分问题是while循环的翻译使用了delay
,在这种情况下我无法理解。下面显而易见的实现可能是错误的,因为它没有延迟计算,而是运行它!
let delay f = f()
没有延迟也阻碍了try...with
和try...finally
。
实际上有两种不同的方法可以在F#中实现连续生成器。一种是使用一元类型表示延迟计算(如果它支持某种表示延迟计算的方式,如kkm所示的Async<'T>
或unit -> option<'T>
类型)
但是,您也可以使用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#转换非常优雅,因为它允许处理延迟的类型(使用返回M<'a>
的Delay
)和仅表示立即结果的类型(利用返回函数的Delay
和Run
)。
根据一元身份,您的delay
应该始终等价于
let delay f = bind (return ()) f
自
val bind : M<'T> -> ('T -> M<'R>) -> M<'R>
val return : 'T -> M<'T>
delay
具有的签名
val delay : (unit -> M<'R>) -> M<'R>
CCD_ 30是类型绑定到CCD_。请注意,您的bind
函数的参数与传统的bind p rest
顺序相反。这在技术上是相同的,但确实使读取代码变得复杂。
由于您将一元类型定义为type Maybe<'a> = option<'a>
,因此不会延迟计算,因为该类型根本不封装任何计算,只封装一个值。所以你们把延迟定义为let delay f = f()
在理论上是正确的。但对于while循环来说,这是不够的:循环的"主体"将在其"测试条件"之前计算,实际上是在绑定bind
之前。为了避免这种情况,您用一层额外的延迟来重新定义您的monad:您不是包装一个值,而是包装一个以单位为单位并计算该值的计算。
type Maybe<'a> = unit -> option<'a>
let return x = fun () -> Some(x)
let fail = fun() -> None
let bind p rest =
match p() with
| None -> fail
| Some r -> rest r
请注意,只有在bind
函数内部才会运行封装的计算,也就是说,只有在绑定了bind
的参数之后才运行。
利用上述表达式,delay
被正确地简化为
let delay f = fun () -> f()