3

クラスのメンバー関数をメモ化しようとしていますが、メンバーが (別のメンバーによって) 呼び出されるたびに、まったく新しいキャッシュと「メモ化された」関数が作成されます。

member x.internal_dec_rates = 
        let cache = new Dictionary< Basis*(DateTime option), float*float>()
        fun (basis:Basis) (tl:DateTime option) ->
            match cache.TryGetValue((basis,tl)) with
            | true, (sgl_mux, sgl_lps) -> (sgl_mux, sgl_lps)
            | _ ->
                let (sgl_mux, sgl_lps) =
                    (* Bunch of stuff *)
                cache.Add((basis,tl),(sgl_mux,sgl_lps))
                sgl_mux,sgl_lps

「実世界の関数型プログラミング」のリスト 10.5 をモデルとして使用しています。メモ化の高階関数を使用してみましたが、役に立ちません。上記のリストには、メモ化が直接組み込まれています。

問題は、私がそれを呼び出すときです。

member x.px (basis:Basis) (tl: DateTime option) = 
        let (q,l) = (x.internal_dec_rates basis tl)
        let (q2,l2) = (x.internal_dec_rates basis tl)
        (exp -q)*(1.-l)

実行は「let cache=...」行に進み、ポイント全体を無効にします。スコープの問題ではないことを確認するために (q2,l2) 行を挿入しましたが、そうではないようです。

実際、メンバー関数として Petricek のコードを使用してテストを行ったところ、同じ問題があるようです。

// Not a member function
let memo1 f =
    let cache = new Dictionary<_,_>()
    (fun x ->
        match cache.TryGetValue(x) with
        | true, v -> v
        | _ -> let v = f x
               cache.Add(x,v)
               v
    )

member x.factorial = memo1(fun y->
    if (y<=0) then 1 else y*x.factorial(y-1))

x.factorial の内部再帰でさえ、レベルごとに新しい「キャッシュ」を設定しているようです。

私は何を間違っていますか、どうすればこれを機能させることができますか?

4

6 に答える 6

6

ジャックの答えに対するあなたのコメントに応えて、これは退屈になる必要はありません。memoize 関数が与えられた場合:

let memoize f =
  let cache = Dictionary()
  fun x ->
    match cache.TryGetValue(x) with
    | true, v -> v
    | _ -> 
      let v = f x
      cache.Add(x, v)
      v

各関数を let バインドされた値として定義し、メソッドから返します。

type T() as x =
  let internalDecRates = memoize <| fun (basis: Basis, tl: DateTime option) ->
    (* compute result *)
    Unchecked.defaultof<float * float>

  let px = memoize <| fun (basis, tl) ->
    let (q,l) = x.InternalDecRates(basis, tl)
    let (q2,l2) = x.InternalDecRates(basis, tl)
    (exp -q)*(1.-l)

  member x.InternalDecRates = internalDecRates
  member x.Px = px

唯一の「ボイラープレート」はletバインディングと への呼び出しmemoizeです。

編集:kvbが指摘したように、F# 3.0の自動プロパティでは、より簡潔なソリューションが可能です:

type T() as x =
  member val InternalDecRates = memoize <| fun (basis: Basis, tl: DateTime option) ->
    (* compute result *)
    Unchecked.defaultof<float * float>

  member val Px = memoize <| fun (basis, tl) ->
    let (q,l) = x.InternalDecRates(basis, tl)
    let (q2,l2) = x.InternalDecRates(basis, tl)
    (exp -q)*(1.-l)
于 2012-08-07T14:29:05.670 に答える
5

ここには長い答えがたくさんあります。短い答えはそれです

member x.P = code()

がアクセスされるたびに実行されるgetterPを持つプロパティを定義します。一度だけ実行されるように、キャッシュの作成をクラスのコンストラクターに移動する必要があります。code()P

于 2012-08-07T15:54:37.673 に答える
2

cacheJohn の言うとおりです。辞書を、型の非公開の let バインド メンバーに移動する必要があります。

型メンバーは、モジュール内のバインドされた値とは少し異なる方法でコンパイルさletれます。これが動作の違いの理由です。メソッドの本体をコピーして貼り付け、それをモジュール内のバインドされた値にx.internal_dec_rates割り当てると、正しく動作するはずです。F# コンパイラは、一度作成されてからフィールドに割り当てられるクロージャとしてコンパイルするためです。モジュール。letstatic readonly

適切な測定のために、他のいくつかのヒント:

  • memberメソッドはオプションのパラメーターを使用できるため、必要に応じてメソッド シグネチャを少し単純化できます。
  • キャッシュ キーは一度だけ作成して再利用できます (これは間違いを防ぐのにも役立ちます)。
  • とにかくタプル全体を返すだけなので(sgl_mux, sgl_lps)、タプルに名前 (例: ) を割り当てるだけで、パターン マッチング コードを簡略化できます。value

あなたのコードに対する私の見解は次のとおりです。

type FooBar () =
    let cache = new Dictionary< Basis*(DateTime option), float*float>()

    member x.internal_dec_rates (basis : Basis, ?tl : DateTime) =
        let key = basis, tl
        match cache.TryGetValue key with
        | true, value -> value
        | _ ->
            // sgl_mux, sgl_lps
            let value =
                (* Bunch of stuff *)

            cache.Add (key, value)
            value
于 2012-08-07T12:32:52.450 に答える
1

他の回答に加えて、F# 3.0 では、自動的に実装されたプロパティを使用できることに注意してください。これは、必要に応じて動作します。

member val internal_dec_rates = ...

ここで、右辺は 1 回だけ評価されますが、すべて自己完結型です。

于 2012-08-07T14:54:31.110 に答える
1

辞書を関数呼び出しの外に移動する必要があります-のように

let cache = new Dictionary< Basis*(DateTime option), float*float>()
member x.internal_dec_rates =             
        fun (basis:Basis) (tl:DateTime option) ->
            match cache.TryGetValue((basis,tl)) with
            | true, (sgl_mux, sgl_lps) -> (sgl_mux, sgl_lps)
            | _ ->
                let (sgl_mux, sgl_lps) =
                    (* Bunch of stuff *)
                cache.Add((basis,tl),(sgl_mux,sgl_lps))
                sgl_mux,sgl_lps

このようにして、関数呼び出し間でキャッシュが保持されます。同じmemo1問題があります。元のバージョンでは、関数を呼び出すたびに新しいキャッシュを作成していました。この方法では、関数呼び出し間で持続する単一のキャッシュが得られます。

于 2012-08-07T12:17:36.360 に答える