在F#中计算表达式中`while`循环的作用是什么?

Mar*_*rot 7 syntax f# computation-expression

如果定义While构建器对象的方法,则可以while计算表达式中使用-loops .该While方法的签名是:

member b.While (predicate:unit->bool, body:M<'a>) : M<'a>
Run Code Online (Sandbox Code Playgroud)

为了比较,该For方法的签名是:

member b.For (items:seq<'a>, body:unit->M<'a>) : M<'a>
Run Code Online (Sandbox Code Playgroud)

您应该注意到,在While-method中,body是一个简单类型,而不是方法中的函数For.

你可以let在计算表达式中嵌入一​​些其他语句,比如函数调用,但是那些不可能在while-loop中不可能执行多次.

builder {
    while foo() do
      printfn "step"
      yield bar()
}
Run Code Online (Sandbox Code Playgroud)

为什么while-loop不会被执行多次,而只是重复?为什么与for循环有显着差异?更好的是,是否有一些在计算表达式中使用while循环的策略?

kvb*_*kvb 4

如果您查看计算表达式的求值方式,您会发现

while foo() do
  printfn "step"
  yield bar()
Run Code Online (Sandbox Code Playgroud)

被翻译成类似的东西

builder.While(fun () -> foo(), 
              builder.Delay(fun () -> 
                              printfn "step"
                              builder.Yield(bar()))))
Run Code Online (Sandbox Code Playgroud)

这种转换允许对 while 循环体进行多次求值。虽然您的类型签名对于某些计算表达式(例如seqasync)来说是准确的,但请注意,插入调用Delay可能会导致不同的签名。例如,您可以像这样定义一个列表生成器:

type ListBuilder() =
  member x.Delay f = f
  member x.While(f, l) = if f() then l() @ (x.While(f, l)) else []
  member x.Yield(i) = [i]
  member x.Combine(l1,l2) = l1 @ l2()
  member x.Zero() = []
  member x.Run f = f()

let list = ListBuilder()
Run Code Online (Sandbox Code Playgroud)

现在您可以计算如下表达式:

list {
  let x = ref 0
  while !x < 10 do
    yield !x
    x := !x + 1
}
Run Code Online (Sandbox Code Playgroud)

得到相当于[0 .. 9].

在这里,我们的While方法具有签名(unit -> bool) * (unit -> 'a list) -> 'a list,而不是(unit -> bool) * 'a list -> 'a list。一般来说,当Delay操作的类型为时(unit -> M<'a>) -> D<M<'a>>While方法的签名将为(unit -> bool) * D<M<'a>> -> M<'a>