F# 如何在计算生成器中实现延迟?

F# 如何在计算生成器中实现延迟?,f#,computation-expression,F#,Computation Expression,以下是我到目前为止的情况: 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 matc

以下是我到目前为止的情况:

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)
        )

没有延迟也会妨碍
try…with
try…finally

根据一元身份,您的
延迟应始终等于

let delay f = bind (return ()) f

val bind : M<'T> -> ('T -> M<'R>) -> M<'R>
val return : 'T -> M<'T>
'T
被类型绑定到
单元
。请注意,
bind
函数的参数与常规顺序
bind p rest
相反。这在技术上是相同的,但会使读取代码变得复杂

由于将一元类型定义为
类型Maybe
,因此不会延迟计算,因为该类型根本不包装任何计算,只包装一个值。因此,您将延迟定义为
让延迟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

在F#中,实际上有两种不同的实现延续生成器的方法。一种是使用一元类型表示延迟计算(如果它支持某种表示延迟计算的方式,如kkm所示的
Async
类型)

但是,您也可以使用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#compiler在需要延迟的计算中使用
Delay
,即:1)包装整个计算,2)顺序组合计算时,例如在计算中使用
if
,3)延迟
的主体,而
for


在上面的定义中,
Delay
成员返回
unit->M
,但这很好,因为
组合在一起,而
获取
unit->M您是否尝试过
让Delay f=fun()->f()
?您是否查看了FSharpx中的
Maybe
monad实现?请参阅一种方法。特别是,
延迟f=f
运行f=f()
应该可以工作,假设您在
合并
时调整
的类型,依此类推。@Joh您可以使用类型
(单位->M)
(如@kvb所建议的)来定义
延迟。如果修改其余定义以匹配此类型,则一切都将正常工作。有关更复杂的定义,请参见我的答案。(诀窍在于计算生成器可以简单地使用不同的类型进行非延迟和延迟计算)。感谢您的回答,它证实了我的猜测:我需要在延续的基础上进行构建。我仍然对
for
while
之间的差异感到惊讶。对我来说,这看起来有点不一致。@kkm只是出于好奇,对于“延迟应该始终等于…”这句话,你有什么参考资料吗?在实现F#计算表达式时,有一个非常好的替代定义,但如果类似的问题已经在其他地方出现,我会非常感兴趣(我想Haskell没有这个问题,理论上的“单子”也没有任何延迟…@TomasPetricek:对,我混合了理论上的单子和F#(渴望)实施延迟的额外身份出现在Syme et al Expert F#(我在家里有一本旧书(不是2.0版)第9章)中。我从来没有完全探索过,如果违反了这一条,会发生什么,所以为了安全起见,我一直坚持这样做。我更喜欢你的方法,真的@谢谢你的参考!我认为我的方法在定义
Delay
使用
Bind
(并使用
M@kkm我特别好奇地想知道在其他语言中是否存在类似于
Delay
的东西……答案很好。这只是Haskell的懒惰在这里起作用吗?一个while循环在Haskell中甚至没有意义,因为它依赖于副作用,对吗?另外,我有点惊讶于你的
Zero
Combine
与Haskell中典型的
Maybe
MonadPlus
实例不太相似,尽管我想这都是一个关于你期望的语义的问题。@kvb关于
while
循环的好观点。我想
while
循环在monad的上下文中可能是有意义的,因为monad可能提供副作用ects使这种构造在Haskell中很有用。但条件函数也必须是一元函数。@kvb关于
Combine
Zero
-我认为它们出现在两个不同的上下文中。如果您的monad可以返回多个结果,您可以将它们类似于
MonadPlus
(这可能是使用
yield
编写的)。然后
Combine
组合值,而
Zero
不返回值。但是,它们也可以(如此处)用于副作用计算的顺序合成。此处,
Zero
返回不返回的计算(在执行副作用后)和
Combine
组合具有副作用且不返回的计算(第一个参数)使用返回值的计算。多亏了Tomas。我假设我必须遵守文档中的签名,但这些签名似乎只是一个指南,而不是严格的规则。真正重要的是,所有方法都可以按照第二个表中描述的方式进行组合,对吗?
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
let delay f = fun () -> f() 
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()
// 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 }