2014-12-12 5 views
8

Diciamo che si dispone di una funzione ricorsiva definita in un blocco let:C'è un modo più semplice per memoizzare un let fn ricorsivo?

(let [fib (fn fib [n] 
      (if (< n 2) 
       n 
       (+ (fib (- n 1)) 
       (fib (- n 2)))))] 
    (fib 42)) 

Questo può essere trasformato meccanicamente di utilizzare memoize:

  1. Wrap forma fn in una chiamata a memoize.
  2. Sposta il nome della funzione come primo argomento.
  3. Passa la funzione in se stessa ovunque venga chiamata.
  4. Ricollegare il simbolo della funzione per fare lo stesso utilizzando partial.

Trasformare il codice di cui sopra porta a:

(let [fib (memoize 
      (fn [fib n] 
       (if (< n 2) 
       n 
       (+ (fib fib (- n 1)) 
        (fib fib (- n 2)))))) 
     fib (partial fib fib)] 
    (fib 42)) 

Questo funziona, ma si sente troppo complicato. La domanda è: c'è un modo più semplice?

+0

In questo caso è necessario utilizzare il TCO (utilizzando 'loop' come Clojure non ha TCO nella definizione comune). – Hauleth

+0

@hauleth Questa domanda riguarda come applicare la memoizzazione a una funzione ricorsiva arbitraria definita nel contesto di un blocco let. Questa particolare formulazione di Fibonacci ricorsiva dell'albero viene utilizzata solo perché è un semplice esempio ben noto che illustra i vantaggi della memoizzazione. Sicuramente, il calcolo stack-friendly dei numeri di Fibonacci avrebbe un approccio diverso. –

risposta

3

Ho dei rischi nel rispondere poiché non sono uno studioso ma non la penso così. Hai praticamente fatto la cosa standard che in fine è un'applicazione parziale della memoizzazione attraverso un combinatore a virgola fissa.

Si potrebbe provare a giocherellare con i macro però (per i casi semplici potrebbe essere facile, la sintassi-preventivo farebbe la risoluzione dei nomi per voi e potreste operare su quello). Ci proverò una volta tornato a casa.

edit: tornò a casa e ha cercato fuori roba, questo sembra essere ok-ish per i casi semplici

(defmacro memoize-rec [form] 
    (let [[fn* fname params & body] form 
     params-with-fname (vec (cons fname params))] 
    `(let [f# (memoize (fn ~params-with-fname 
         (let [~fname (partial ~fname ~fname)] [email protected])))] 
     (partial f# f#)))) 

;; (clojure.pprint/pprint (macroexpand '(memoize-rec (fn f [x] (str (f x)))))) 
((memoize-rec (fn fib [n] 
       (if (< n 2) 
        n 
        (+ (fib (- n 1)) 
        (fib (- n 2)))))) 75) ;; instant 

((fn fib [n] 
       (if (< n 2) 
        n 
        (+ (fib (- n 1)) 
        (fib (- n 2))))) 75) ;; slooooooow 

più semplice di quello che ho pensato!

+0

L'aspetto del combinatore di punti fissi è interessante. Forse qualsiasi approccio alla ricorsività indiretta tramite il risultato della chiamata 'memoize' userà un tale combinatore o in alternativa introdurrà qualche stato globale (ad esempio, questo problema non si verifica quando non si trova in un blocco let e in' def' ing a var, che fornisce l'indiretta necessaria.) Quindi probabilmente non esiste una soluzione _fundamentalmente più semplice. Ma, sintatticamente, la tua macro è fantastica. Si prende cura con cura della trasformazione meccanica (senza essere una macro che cammina con il codice!), E porta a un risultato che è semplice come una chiamata "memoize". –

1

Non sono sicuro che questo sia "più semplice" di per sé, ma ho pensato di condividere un approccio che ho preso per ri-implementare letfn per un trasformatore CPS che ho scritto.

La chiave è introdurre le variabili, ma ritardare l'assegnazione dei valori finché non sono tutti nell'ambito. Fondamentalmente, ciò che si desidera scrivere è:

(let [f nil] (set! f (memoize (fn [] <body-of-f>))) (f))

Naturalmente questo non funziona come è, perché let attacchi sono immutabili in Clojure. Siamo in grado di andare in giro che, però, utilizzando un tipo di riferimento - ad esempio, un promise:

(let [f (promise)] (deliver! f (memoize (fn [] <body-of-f>))) (@f))

Ma questo cade ancora breve, perché dobbiamo sostituire ogni istanza di f in <body-of-f> con (deref f). Ma possiamo risolvere questo problema introducendo un'altra funzione che richiama la funzione memorizzata nella promessa.Così l'intera soluzione potrebbe essere simile a questo:

(let [f* (promise)] (letfn [(f [] (@f*))] (deliver f* (memoize (fn [] <body-of-f>))) (f)))

Se si dispone di un insieme di funzioni ricorsive reciprocamente:

(let [f* (promise) g* (promise)] (letfn [(f [] (@f*)) (g [] (@g*))] (deliver f* (memoize (fn [] (g)))) (deliver g* (memoize (fn [] (f)))) (f)))

Ovviamente questo è un sacco di caldaia-piastra. Ma è piuttosto semplice costruire una macro che ti dia la sintassi in stile letfn.

Problemi correlati