如何让F#计算表达式实现类似seq的延迟执行效果?
自定义IO计算表达式的延迟执行问题
1. Seq的延迟执行参考示例
let foo x = seq { printfn $"1{x}" 1 } let bar = foo 1
运行上述代码时,控制台无任何输出,只有当实际评估序列中的值(如调用Seq.head bar)时,才会执行printfn完成打印。
2. 自定义IO计算表达式的即时执行问题
let program1 (x: int) = IOProgram.programBuilder { printfn "start" do! print $"the number is {x}" return x.ToString() } let execution = program1 1
创建execution变量时,控制台立刻打印了"start",而非等到解释执行该计算表达式时才触发IO操作,不符合预期的延迟行为。
3. 尝试解决的过程
为基于Free Monad的计算表达式添加Delay方法:
type IOProgramBuilder () = member this.Bind (x, f) = bind f x member this.Return x = Pure x member this.ReturnFrom x = x member this.Zero () = Pure () member this.Delay (f : Unit -> 'a) = f let programBuilder = IOProgramBuilder ()
但计算表达式的返回类型变为Unit -> IOProgram<'a>,若仅在解释器中调用会十分繁琐,不符合seq返回seq<'a>的简洁形式。
若添加Run方法抵消类型变化:
type IOProgramBuilder () = // 保留其他成员定义 member this.Run (f) = f()
则会直接抵消Delay的延迟效果,回到即时执行的问题。
核心疑问
- 有资料建议将内部类型包装为
unit -> 'a(或Lazy<'a>),这是否是正确的解决方案?有没有更简洁的实现方式? - 对比seq的以下行为:
let foo x = seq { printfn $"!{x}" 1 } let bar = (foo 1).GetEnumerator()
调用GetEnumerator()时控制台仍然无输出,但自定义计算表达式中类似“延迟”操作却会立即打印"start"。请问seq是同时延迟了枚举器的创建和每个bind的执行吗?
内容的提问来源于stack exchange,提问作者MrD at KookerellaLtd
相关产品推荐
相关产品推荐

