10

これが私がこれまでに持っているものです:

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)
        )

whileLoopループをサポートするために正常に動作しforますが、whileループがサポートされるようにする方法がわかりません。問題の一部は、whileループの変換でが使用delayされることですが、この場合は理解できませんでした。以下の明らかな実装は、計算を遅らせることはなく、代わりに実行するため、おそらく間違っています。

let delay f = f()

遅延がないことも妨げtry...withになりtry...finallyます。

4

2 に答える 2

12

F#で継続ビルダーを実装するには、実際には2つの異なる方法があります。1つは、モナディックタイプを使用して遅延計算を表すことです( kkmで示されるタイプAsync<'T>など、遅延計算を表す何らかの方法をサポートしている場合。unit -> option<'T>

ただし、F#計算式の柔軟性を使用して、の戻り値として別の型を使用することもできますDelayCombine次に、それに応じて操作を変更し、メンバーも実装する必要があります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()

秘訣は、Delay遅延が必要な計算がある場合にF#コンパイラが使用することです。つまり、1)計算全体をラップする場合、2)計算を順番に構成する場合、たとえば計算内で使用する場合、3)またはifの本体を遅延させる場合です。 。whilefor

上記の定義では、Delayメンバーはunit -> M<'a>の代わりに戻りますが、2番目の引数としてとを使用するM<'a>ため、これはまったく問題ありません。さらに、関数を評価するを追加することにより、ブロック全体が:に渡されるため、ブロックの結果(遅延関数)が評価されます。CombineWhileunit -> M<'a>Runmaybe { .. }Run

// 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 }

これは、遅延のない型の計算ビルダーを定義する方法であり、関数内で型をラップするよりも効率的である可能性が高く(kkmのソリューションのように)、型の特別な遅延バージョンを定義する必要はありません。

この問題は、たとえばHaskellでは発生しないことに注意してください。これは、怠惰な言語であるため、計算を明示的に遅らせる必要がないためです。F#変換は、遅延する型(Delayreturnsを使用M<'a>)と即時結果を表す型(Delay関数&を返すを使用)の両方を処理できるため、非常に洗練されていると思いますRun

于 2012-01-28T16:40:58.240 に答える
5

モナディックアイデンティティによると、あなたdelayは常にと同等でなければなりません

let delay f = bind (return ()) f

以来

val bind : M<'T> -> ('T -> M<'R>) -> M<'R>
val return : 'T -> M<'T>

delay署名があります

val delay : (unit -> M<'R>) -> M<'R>

'Tに型バインドされていunitます。bind関数の引数は通常の順序とは逆になっていることに注意してくださいbind p rest。これは技術的には同じですが、コードの読み取りが複雑になります。

モナディック型をとして定義しているのでtype Maybe<'a> = option<'a>、型は計算をまったくラップせず、値のみをラップするため、計算を遅らせることはありません。let delay f = f()したがって、理論的に正しいように遅延を定義します。ただし、whileループには適切ではありません。ループの「本体」は、「テスト条件」の前、実際にbindはバインドされる前に計算されます。これを回避するには、遅延の追加レイヤーを使用してモナドを再定義します。値をラップする代わりに、単位を取り、値を計算する計算をラップします。

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

ラップされた計算は、bind関数内まで実行されないことに注意してください。つまり、引数bindがバインドされるまで実行されません。

上記の式で、delayは正しく簡略化されます

let delay f = fun () -> f() 
于 2012-01-28T02:19:48.147 に答える