10

这是我到目前为止所拥有的:

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

4

2 回答 2

12

在 F# 中实现延续构建器实际上有两种不同的方式。一种是使用 monadic 类型来表示延迟计算(如果它支持某种表示延迟计算的方式,例如Async<'T>kkm 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)延迟whilefor.

在上面的定义中,Delay成员返回unit -> M<'a>而不是M<'a>,但这很好,因为CombineWhile作为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)。

于 2012-01-28T16:40:58.240 回答
5

根据一元身份,你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>

'T类型绑定到unit. 请注意,您的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() 
于 2012-01-28T02:19:48.147 回答