我看到的典型Pause monad实现看起来像这样(基于Giulia Costantini和Giuseppe Maggiore的Friendly F#的第5章)。

open System

type Process<'a> = unit -> 'a Step
and Step<'a> =
| Continue of 'a
| Paused of 'a Process

type PauseMonad () =
    member this.Return x = fun () -> Continue x
    member this.ReturnFrom x = x
    member this.Bind (result, rest) =
        fun () ->
            match result () with
            | Continue x -> rest x ()
            | Paused p -> Paused (this.Bind (p, rest))

let yield_ () =
    fun () ->
        Paused (fun () ->
            Continue ())

let get_process_step process_ step = do printfn "Process %d, step %d." process_ step
let get_last_process_step process_ = do printfn "Process %d finished." process_

let rec get_process process_ step_count =
    PauseMonad () {
        do! yield_ ()
        if step_count = 0 then
            do get_last_process_step process_
            return ()
        else
            do get_process_step process_ step_count
            return! get_process process_ <| step_count - 1
    }

let rec race p1 p2 =
    match p1 (), p2 () with
    | Continue _, _ -> do printfn "Process 1 finished first."
    | _, Continue _ -> do printfn "Process 2 finished first."
    | Paused p1_, Paused p2_ -> race (p1_) (p2_)

[<EntryPoint>]
let main _ =
    let process_1 = get_process 1 5
    let process_2 = get_process 2 7
    do race process_1 process_2
    0


Here是Haskell中的类似实现。

但是,摆脱相互递归类型Process和Step似乎更简单,而仅使用一个递归类型Process即可,如下所示。

open System

type Process<'a> =
| Continue of 'a
| Paused of (unit -> 'a Process)

type PauseMonad () =
    member this.Return x = Continue x
    member this.ReturnFrom x = x
    member this.Bind (result, rest) =
        match result with
        | Continue x -> Paused (fun () -> rest x)
        | Paused p -> Paused (fun () -> this.Bind (p (), rest))

let yield_ () =
    Paused (fun () ->
        Continue ())

let get_process_step process_ step = do printfn "Process %d, step %d." process_ step
let get_last_process_step process_ = do printfn "Process %d finished." process_

let rec get_process process_ step_count =
    PauseMonad () {
        do! yield_ ()
        if step_count = 0 then
            do get_last_process_step process_
            return ()
        else
            do get_process_step process_ step_count
            return! get_process process_ <| step_count - 1
    }

let rec race p1 p2 =
    match p1, p2 with
    | Continue _, _ -> do printfn "Process 1 finished first."
    | _, Continue _ -> do printfn "Process 2 finished first."
    | Paused p1_, Paused p2_ -> race (p1_ ()) (p2_ ())

[<EntryPoint>]
let main _ =
    let process_1 = get_process 1 5
    let process_2 = get_process 2 7
    do race process_1 process_2
    0


这些实现中的任何一个都给我相同的输出:

Process 1, step 5.
Process 2, step 7.
Process 1, step 4.
Process 2, step 6.
Process 1, step 3.
Process 2, step 5.
Process 1, step 2.
Process 2, step 4.
Process 1, step 1.
Process 2, step 3.
Process 1 finished.
Process 2, step 2.
Process 1 finished first.


我已经使这两种实现尽可能相似,以促进区别。据我所知,唯一的区别是:


在第一个版本中,yield_,PauseMonad.Return和PauseMonad.Bind将延迟添加到返回值。在第二个版本中,PauseMonad.Return在Paused包装器中添加了延迟。
在第一个版本中,PauseMonad.Bind运行结果过程的一个步骤,以查看返回值是匹配Continue还是Paused。在第二个版本中,PauseMonad.Bind仅在确定结果与Paused匹配后才运行结果过程的一个步骤。
在第一个版本中,race运行每个过程的一个步骤,检查两个结果是否都与Paused匹配,然后对其余过程进行递归。在第二个版本中,竞赛检查两个进程是否与Paused匹配,然后运行每个进程的一个步骤,并使用这些步骤的返回值递归。


有第一个版本更好的原因吗?

最佳答案

将代码从Haskell转换为F#有点棘手,因为Haskell是惰性的,因此只要您在Haskell中看到任何值(例如'a),就可以将其解释为unit -> 'a(或更准确地说,是Lazy<'a>)-因此一切都隐式地延迟了。

但是,我们只比较一下F#中的两个定义:

// Process is always delayed
type Process1<'a> = unit -> 'a Step1
and Step1<'a> = Continue1 of 'a | Paused1 of 'a Process1

// Process is a value or a delayed computation
type Process2<'a> = Continue2 of 'a | Paused2 of (unit -> 'a Process2)


关键区别在于,当您要表示立即产生一个值的计算时,在第一种情况下,它必须是完全评估的值,但在第二种情况下,它可以是一个执行某些操作并返回值的函数。例如:

let primitive1 : Process1<int> = fun () ->
  printfn "hi!"    // This will print when the computation is evaluated
  Continue1(42) )

let primitive2 : Process2<int> =
  printfn "hi!"    // This will print immediately and returns a monadic value
  Continue2(42)


当您在计算中添加Delay成员时,这变得很有趣,这使您可以编写如下内容,而无需评估副作用:

process {
  printfn "Hi" // Using Process1, we can easily delay this
               // Using Process2, this is trickier (or we run it immediately)
  return 42 }


关于这一点有很多要说的,您可以找到更多信息in a recent article I wrote about computation expressions

10-08 12:40