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 }