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!
This is one of the examples from the Hopac documentation. From what I can see here, even if I do not explicitly call Promise.read j1
or Promise.read j2
the functions still get run. I am wondering if it is possible to defer doing the promised computation until they are actually run? Or should I be using lazy
for the purpose of propagating lazy values?
Looking at the documentation, it does seem like Hopac's promises are supposed to be lazy, but I am not sure how this laziness is supposed to be manifested.
For a demonstration of laziness, consider the following example.
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
Had the values not been memoized, every time the enter is pressed, there would be two Hello
s printed per iteration. This behavior can be seen if memo <|
is removed.
There are some further points worth making. The purpose of Promise.start
is not specifically to get memoizing behavior for some job. Promise.start
is similar to Job.start
in that if you bind a value using let!
or >>=
for example, it won't block the workflow until work is done. However compared to Job.start
, Promise.start
does give an option to wait for the scheduled job to be finished by binding on the nested value. And unlike Job.start
and similarly to regular .NET tasks, it is possible to extract the value from a concurrent job started using Promise.start
.
Lastly, here is an interesting tidbit I've discovered while playing with promises. It turns out, a good way of turning a Job
into an Alt
is to turn it into an Promise
first and then upcast it.
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()
Uncommenting that first case would cause the program to block forever, but if you memoize the expression first that it is possible to get what would be backtracking behavior had the regular alternatives been used.