38 votes

Comment générer des fonctions récursives mémorisées en Clojure ?

J'essaie d'écrire une fonction qui renvoie une fonction récursive mémorisée en Clojure, mais j'ai du mal à faire en sorte que la fonction récursive voie ses propres liaisons mémorisées. Est-ce parce qu'il n'y a pas de var créé ? De même, pourquoi ne puis-je pas utiliser memoize sur la liaison locale créée avec let ?

Ce fabricant de séquences de Fibonacci légèrement inhabituelles qui commencent à un nombre particulier est un exemple de ce que j'aimerais pouvoir faire :

(defn make-fibo [y]
  (memoize (fn fib [x] (if (< x 2)
             y
             (+ (fib (- x 1))
                (fib (- x 2)))))))

(let [f (make-fibo 1)]
  (f 35)) ;; SLOW, not actually memoized

Utilisation de with-local-vars semble être la bonne approche, mais ça ne marche pas pour moi non plus. Je suppose que je ne peux pas fermer sur les variables ?

(defn make-fibo [y]
  (with-local-vars [fib (fn [x] (if (< x 2)
                                  y
                                  (+ (@fib (- x 1))
                                     (@fib (- x 2)))))]
    (memoize fib)))

(let [f (make-fibo 1)]
  (f 35)) ;; Var null/null is unbound!?! 

Je pourrais bien sûr écrire manuellement une macro qui crée un atome fermé et gérer moi-même la mémorisation, mais j'espérais pouvoir le faire sans avoir à recourir à de tels artifices.

0 votes

La solution donnée par @Phelix et @CarlosNunes est sur le Page ClojureDocs pour memoize .

21voto

Rafał Dowgird Points 16600

Il existe un moyen intéressant de le faire, qui ne repose ni sur la réaffectation ni sur le comportement de la fonction def . L'astuce principale consiste à contourner les limites de la récursion en passant une fonction en argument à elle-même :

(defn make-fibo [y]
  (let
    [fib
      (fn [mem-fib x]
         (let [fib (fn [a] (mem-fib mem-fib a))]
           (if (<= x 1)
             y
             (+ (fib (- x 1)) (fib (- x 2))))))
     mem-fib (memoize fib)]

     (partial mem-fib mem-fib)))

Ensuite :

> ((make-fibo 1) 50)
20365011074

Ce qui se passe ici :

  • Le site fib La fonction récursive a reçu un nouvel argument mem-fib . Ce sera la version mémorisée de fib lui-même, une fois qu'il est défini.
  • Le site fib est enveloppé dans un let qui redéfinit les appels à fib afin qu'ils passent le mem-fib jusqu'aux niveaux suivants de récurrence.
  • mem-fib est défini comme mémorisé fib
  • ... et sera adopté par partial comme premier argument à lui-même pour lancer le mécanisme ci-dessus.

Cette astuce est similaire à celle utilisée par le combinateur Y pour calculer le point fixe de la fonction en l'absence d'un mécanisme de récursion intégré.

Étant donné que def "voit le symbole en cours de définition, il y a peu de raisons pratiques d'utiliser cette méthode, sauf peut-être pour créer des fonctions anonymes récursives mémorisées sur place.

19voto

Michał Marczyk Points 54179

Cela semble fonctionner :

(defn make-fibo [y]
  (with-local-vars
      [fib (memoize
            (fn [x]
              (if (< x 2)
                y
                (+ (fib (- x 2)) (fib (dec x))))))]
    (.bindRoot fib @fib)
    @fib))

with-local-vars ne fournit que des liaisons thread-local pour les Vars nouvellement créés, qui sont supprimés une fois que l'exécution quitte le processus d'exécution. with-local-vars d'où la nécessité de .bindRoot .

0 votes

Ding ding ding, merci, nous avons un gagnant ! Mais pourquoi avons-nous dû sauter dans le javaland pour faire le bindRoot ? Plus important encore, cela ne crée-t-il pas un risque de concurrence si deux threads font un .bindRoot presque en même temps, avant que les variables ne soient fermées lorsqu'elles sortent de la portée de cette fonction ? Est-ce que cela reste sûr pour les créations simultanées des fonctions Fibonacci générées ? Ou est-ce que le .bindRoot a une portée lexicale ? Je suis toujours un peu perdu...

0 votes

.bindRoot es synchronized Cependant, cela n'a pas d'importance ici, puisque nous l'appelons sur un Var local qui n'est pas accessible depuis un autre thread à ce stade. Quant à l'aspect javanais d'un appel de méthode, je crois qu'il est inévitable ici ( alter-var-root ne fonctionnera pas, puisqu'il faut un peu de La fixation de la racine doit être déjà en place), mais je ne considère pas cela comme un problème. En fait, je me demande si je ne préfèrerais pas faire la même chose d'une manière qui n'implique pas de Vars locales, mais d'un autre côté, cela semble être une approche particulièrement simple...

0 votes

Merci, je crois que j'ai compris maintenant. L'appel bindRoot crée une liaison Root de la var, cependant cette liaison n'est pas partagée avec les autres threads car ils ont leurs propres liaisons thread-local de la var, et donc le scoping dynamique des var ne nous mord pas le cul. De plus, le bindRoot n'implique pas que la var sera visible depuis le toplevel.

18voto

PheliX Points 539
(def fib (memoize (fn [x] (if (< x 2)
                              x
                              (+ (fib (- x 1))
                                 (fib (- x 2)))))))
(time (fib 35))

0 votes

C'est un style plus typique si vous voulez que la var soit liée à votre espace de nom, mais malheureusement vous avez incorrectement modifié la fonction ! Qu'est-il arrivé au paramètre y ?

3 votes

(fib 2000) donne une StackOverflowError. L'exemple ci-dessus n'utilise pas de récurrence, donc les débordements de pile sont inévitables, à moins que vous ne "chauffiez" la mémorisation en appelant la fonction pour 1 à 2000. Mais comment savoir si 2000 est assez grand pour un cas d'utilisation arbitraire ? C'est là que le bât blesse !

3voto

Carlos Nunes Points 137

Voici la solution la plus simple :

(def fibo
  (memoize (fn [n]
             (if (< n 2)
               n
               (+ (fibo (dec n))
                  (fibo (dec (dec n))))))))

2voto

sortega Points 357

Vous pouvez encapsuler le modèle de fonction récursive mémorisée dans une macro si vous prévoyez de l'utiliser plusieurs fois.

(defmacro defmemo
  [name & fdecl]
  `(def ~name
     (memoize (fn ~fdecl))))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X