2013-12-12 2 views
10

This memoize function schlägt auf alle Funktionen des Typs () -> 'a zur Laufzeit mit einer Null-Argument-Ausnahme.Memoize eine Funktion des Typs() -> '

let memoize f = 
    let cache = System.Collections.Generic.Dictionary() 
    fun x -> 
     if cache.ContainsKey(x) then 
      cache.[x] 
     else 
      let res = f x 
      cache.[x] <- res 
      res 

Gibt es eine Möglichkeit, eine memoize Funktion zu schreiben, die auch für eine () -> 'a funktioniert?

(Meine einzige Alternative für die jetzt einen Lazy Typen verwendet. x.Force() rief den Wert zu erhalten.)

+4

Memoize wird im Allgemeinen verwendet, um Berechnungen für ein Argument zwischenzuspeichern, bei dem das Argument als Suchschlüssel verwendet wird. 'Lazy' ist in diesem Fall das richtige Werkzeug. – Daniel

+2

Sie _could_ wrap 'faul', etwa so:' memoize f = let result = lazy (f()) in fun() -> result.Value' – Daniel

Antwort

11

Der Grund, warum die Funktion fehlschlägt, ist, dass F # Einheit darstellt () mit null vom Typ unit. Das Wörterbuch erlaubt keine null Werte als Schlüssel und so schlägt es fehl.

In Ihrem speziellen Fall gibt es nicht viel Sinn, in memoizing Funktion vom Typ unit -> 'a (weil es besser ist lazy dafür zu benutzen), aber es gibt andere Fälle, in denen dies ein Problem sein würde - None zum Beispiel ist auch vertreten auch durch null so versagt diese:

let f : int option -> int = memoize (fun a -> defaultArg a 42) 
f None 

Der einfache Weg, um dies zu beheben ist, den Schlüssel in einer anderen Datentyp wickeln, um sicherzustellen, dass es nie null:

type Key<'K> = K of 'K 

Dann können Sie wickeln einfach die Taste mit dem K Konstruktor und alles gut funktioniert:

let memoize f = 
    let cache = System.Collections.Generic.Dictionary() 
    fun x -> 
     if cache.ContainsKey(K x) then 
      cache.[K x] 
     else 
      let res = f x 
      cache.[K x] <- res 
      res 
+1

Sollte das nicht 'Dictionary (HashIdentity.Structural)' sein ? – Daniel

+0

Ist es nicht erstellt eine eigene Instanz von 'Cache' Wörterbuch für jede Funktion definiert durch Aufruf von' Memoize'? Nur dadurch können mehrere Schlüssel 'K ' für 'unit -> 'a' ohne einen Konflikt existieren. Da Funktionen den gemeinsamen Cache nicht teilen, kann jedes Literal die Rolle des Schlüssels spielen, und die gesamte Anordnung simuliert effektiv den "faulen" Ansatz. –

+0

@Gene memoize gibt eine Funktion zurück, die über ihre eigene Dictionary-Instanz schließt. memoize Unterschrift ist 'f :('a -> 'b) -> (' a ->' b), wenn 'a: equality' so die Funktion, die es zurückgibt, ist '' a -> b‘ ' Es ist die zurückgegebene Funktion, die Sie verwenden, um Werte zu memoisieren. ZB 'let m = memoize (Spaß x -> x + 1) ;;' gibt 'val m: (int -> int)' –

2

memoization eine reine Funktion (nicht nur vom Typ unit -> 'a, aber andere auch) als Suchschlüssel unmöglich, weil Funktionen im Allgemeinen keinen Gleichheitsvergleich für die reason haben.

Es könnte scheinen, dass für diese spezifische Art von Funktion unit -> 'a es möglich wäre, mit einem benutzerdefinierten Gleichheitsvergleich zu kommen. Der einzige Ansatz zum Implementieren eines solchen Vergleichs über Extreme hinaus (Reflektion, IL, etc.) wäre , wobei die Lookup-Funktion als f1 = f2 iff f1() = f2() aufgerufen wird, was offensichtlich jegliche Leistungsverbesserung, die von Memoisierung erwartet wird, aufhebt.

So, vielleicht, wie bereits erwähnt, sollten für diesen Fall Optimierungen um lazy Muster aufgebaut werden, aber nicht memoization eins.

UPDATE: In der Tat, nach dem zweiten Blick auf die Frage alle oben über Funktionen fehlenden Gleichheitsvergleich ist richtig, aber nicht anwendbar, weil memoization innerhalb jeder einzelnen cache der Funktion von der Schließung geschieht. Auf der anderen Seite ist für diese spezielle Art von Funktionen mit der Signatur unit->'a, d. H. Höchstens ein einzelner Wert des Arguments, die Verwendung von Dictionary mit dem meisten Eintrag ein Overkill. Die folgende ähnlich Stateful, aber einfachere Implementierung mit nur einem Wert memoized tun:

let memoize2 f = 
    let notFilled = ref true 
    let cache = ref Unchecked.defaultof<'a> 
    fun() -> 
     if !notFilled then 
      cache := f() 
      notFilled := false 
     !cache 

verwendet als let foo = memoize2(fun() -> ...heavy on time and/or space calculation...) mit dem ersten Gebrauch foo() Durchführung und das Ergebnis der Berechnung zu speichern und alle aufeinanderfolgenden foo() nur den gespeicherten Wert wiederzuverwenden.

5

Ich fand nur, dass die letzte memoize Funktion on the same website mit Map statt Dictionary Werke für 'a Option -> 'b und () -> 'a zu:

let memoize1 f = 
    let cache = ref Map.empty 
    fun x -> 
     match (!cache).TryFind(x) with 
     | Some res -> res 
     | None -> 
      let res = f x 
      cache := (!cache).Add(x,res) 
      res 
-1

Lösung mit wandelbaren Wörterbuch und einzigen Wörterbuch-Lookup Aufruf: let memoize1 f = // printfn "Dictionary" let cache = System.Collections.Generic.Dictionary() fun x -> let result, value = cache.TryGetValue(x) match result with | true -> value | false -> // printfn "f x" let res = f x cache.Add(x, res) res

+1

Dies löst das in der Frage gestellte Problem nicht. 'memoize1 (fun() -> 1)()' scheitert auch mit 'ArgumentNullException' aus dem gleichen Grund. – kvb