如何让 Promise 变得懒惰?
How to make the Promise lazy?
open System
open System.Threading
open Hopac
open Hopac.Infixes
let hello what = job {
for i=1 to 3 do
do! timeOut (TimeSpan.FromSeconds 1.0)
do printfn "%s" what
}
run <| job {
let! j1 = Promise.start (hello "Hello, from a job!")
do! timeOut (TimeSpan.FromSeconds 0.5)
let! j2 = Promise.start (hello "Hello, from another job!")
//do! Promise.read j1
//do! Promise.read j2
return ()
}
Console.ReadKey()
Hello, from a job!
Hello, from another job!
Hello, from a job!
Hello, from another job!
Hello, from a job!
Hello, from another job!
这是示例之一 from the Hopac documentation。从我在这里看到的情况来看,即使我没有明确调用 Promise.read j1
或 Promise.read j2
,函数仍然会得到 运行。我想知道是否有可能推迟进行承诺的计算,直到它们实际上是 运行?还是我应该使用 lazy
来传播惰性值?
看文档,Hopac 的 promises 似乎是懒惰的,但我不确定这种懒惰应该如何表现。
为了证明懒惰,请考虑以下示例。
module HopacArith
open System
open Hopac
type S = S of int * Promise<S>
let rec arith i : Promise<S> = memo <| Job.delay(fun () ->
printfn "Hello"
S(i,arith (i+1)) |> Job.result
)
let rec loop k = job {
let! (S(i,_)) = k
let! (S(i,k)) = k
printfn "%i" i
Console.ReadKey()
return! loop k
}
loop (arith 0) |> run
Hello
0
Hello
1
Hello
2
如果这些值没有被记忆,每次按下回车键,每次迭代都会打印两个 Hello
s。如果删除了 memo <|
,就会看到这种行为。
还有一些值得进一步说明的要点。 Promise.start
的目的不是专门为某些工作获取记忆行为。 Promise.start
与 Job.start
的相似之处在于,如果您使用 let!
或 >>=
绑定一个值,它在工作完成之前不会阻塞工作流。然而,与 Job.start
相比,Promise.start
确实提供了一个选项,可以通过绑定嵌套值来等待计划的作业完成。与 Job.start
不同并且类似于常规 .NET 任务,可以从使用 Promise.start
.
开始的并发作业中提取值
最后,这是我在玩 promises 时发现的一个有趣的花絮。事实证明,将 Job
转换为 Alt
的一个好方法是先将其转换为 Promise
,然后再向上转换。
module HopacPromiseNonblocking
open System
open Hopac
open Hopac.Infixes
Alt.choose [
//Alt.always 1 ^=>. Alt.never () // blocks forever
memo (Alt.always 1 ^=>. Alt.never ()) :> _ Alt // does not block
Alt.always 1 >>=*. Alt.never () :> _ Alt // same as above, does not block
Alt.always 2
]
|> run
|> printfn "%i" // prints 2
Console.ReadKey()
取消对第一种情况的注释会导致程序永远阻塞,但如果您首先记住表达式,那么如果使用常规替代方案,则可能会出现回溯行为。
open System
open System.Threading
open Hopac
open Hopac.Infixes
let hello what = job {
for i=1 to 3 do
do! timeOut (TimeSpan.FromSeconds 1.0)
do printfn "%s" what
}
run <| job {
let! j1 = Promise.start (hello "Hello, from a job!")
do! timeOut (TimeSpan.FromSeconds 0.5)
let! j2 = Promise.start (hello "Hello, from another job!")
//do! Promise.read j1
//do! Promise.read j2
return ()
}
Console.ReadKey()
Hello, from a job!
Hello, from another job!
Hello, from a job!
Hello, from another job!
Hello, from a job!
Hello, from another job!
这是示例之一 from the Hopac documentation。从我在这里看到的情况来看,即使我没有明确调用 Promise.read j1
或 Promise.read j2
,函数仍然会得到 运行。我想知道是否有可能推迟进行承诺的计算,直到它们实际上是 运行?还是我应该使用 lazy
来传播惰性值?
看文档,Hopac 的 promises 似乎是懒惰的,但我不确定这种懒惰应该如何表现。
为了证明懒惰,请考虑以下示例。
module HopacArith
open System
open Hopac
type S = S of int * Promise<S>
let rec arith i : Promise<S> = memo <| Job.delay(fun () ->
printfn "Hello"
S(i,arith (i+1)) |> Job.result
)
let rec loop k = job {
let! (S(i,_)) = k
let! (S(i,k)) = k
printfn "%i" i
Console.ReadKey()
return! loop k
}
loop (arith 0) |> run
Hello
0
Hello
1
Hello
2
如果这些值没有被记忆,每次按下回车键,每次迭代都会打印两个 Hello
s。如果删除了 memo <|
,就会看到这种行为。
还有一些值得进一步说明的要点。 Promise.start
的目的不是专门为某些工作获取记忆行为。 Promise.start
与 Job.start
的相似之处在于,如果您使用 let!
或 >>=
绑定一个值,它在工作完成之前不会阻塞工作流。然而,与 Job.start
相比,Promise.start
确实提供了一个选项,可以通过绑定嵌套值来等待计划的作业完成。与 Job.start
不同并且类似于常规 .NET 任务,可以从使用 Promise.start
.
最后,这是我在玩 promises 时发现的一个有趣的花絮。事实证明,将 Job
转换为 Alt
的一个好方法是先将其转换为 Promise
,然后再向上转换。
module HopacPromiseNonblocking
open System
open Hopac
open Hopac.Infixes
Alt.choose [
//Alt.always 1 ^=>. Alt.never () // blocks forever
memo (Alt.always 1 ^=>. Alt.never ()) :> _ Alt // does not block
Alt.always 1 >>=*. Alt.never () :> _ Alt // same as above, does not block
Alt.always 2
]
|> run
|> printfn "%i" // prints 2
Console.ReadKey()
取消对第一种情况的注释会导致程序永远阻塞,但如果您首先记住表达式,那么如果使用常规替代方案,则可能会出现回溯行为。