Clojure: Simple factorielle provoque un débordement de pile
Ce que je fais mal? Simple récursivité quelques milliers d'appels profonde jette un StackOverflowError
.
Si la limite de Clojure récurrences est si bas, comment puis-je compter sur elle?
(defn fact[x]
(if (<= x 1) 1 (* x (fact (- x 1)) )))
user=> (fact 2)
2
user=> (fact 4)
24
user=> (fact 4000)
java.lang.StackOverflowError (NO_SOURCE_FILE:0)
Vous devez vous connecter pour publier un commentaire.
La taille de la pile, je comprends, dépend de la JVM vous utilisez ainsi que la plate-forme. Si vous utilisez la JVM de Sun, vous pouvez utiliser le
-Xss
et-XThreadStackSize
paramètres pour définir la taille de la pile.La meilleure façon de faire de la récursivité en Clojure est bien pour utiliser
loop
/recur
:Clojure va faire la queue-appel d'optimisation pour cela; ceci assure que vous ne serez jamais à court dans
StackOverflowError
s.Et en raison
défn
implique unboucle
de liaison, vous pouvez omettre leloop
expression, et d'utiliser ses arguments comme argument de la fonction. Et pour en faire un 1 argument de la fonction, l'utilisation lemultiary
caractéristiques des fonctions:Edit: Pour l'histoire, voici un Clojure une fonction qui retourne un paresseux ordre de tous les factorielles:
(def facts (lazy-cat [1] (map * facts (iterate inc 2))))
. Puis(take 5 facts)
produit(1 2 6 24 120)
.(def facts (reductions * (iterate inc 1)))
that ensures that you’ll never run into StackOverflowErrors
jamais? vraiment?Voici une autre façon:
Ce ne sera pas le coup de la pile car
range
retourne un paresseux seq, etreduce
des promenades à travers la seq sans se tenant sur la tête.reduce
rend l'utilisation de fragments de seqs si l'on peut, si cela peut réellement faire mieux que l'aiderecur
vous-même. À l'aide de Siddhartha Reddyrecur
version et cereduce
version:Une légère différence. J'aime bien laisser mon
recur
anneau demap
etreduce
et les amis, qui sont à la fois plus lisible et explicite, et l'utilisationrecur
à l'interne un peu plus élégamment que je suis susceptible de le faire à la main. Il ya des moments où vous avez besoin derecur
manuellement, mais pas beaucoup dans mon expérience.(defn factorial [n] (apply * (range 1 (inc n))))
(defn factorial [n] (reduce *' (range 1 (inc n))))
Note la " marque à côté de l' *.Clojure a plusieurs façons de le contournement de la récursivité
ps: je n'ai pas de repl sur moi, donc quelqu'un de bien vouloir tester-fixer le trapoline fait fonction?
(defn fact ([x] (fact x nil)) ([ x lastResult] (if (<= x 1) (if (nil? lastResult) 1 lastResult) (if (nil? lastResult) (fact (dec x) x) (fact (dec x) (* lastResult x ))) )) )
Btw trampoline est appelée avec (trampoline fn arg), de sorte que la parenthèse autour de fact (42) devrait être suppriméQue je m'apprêtais à poster la suite, je vois que c'est presque le même que le Schéma de l'exemple posté par JasonTrue... de toute façon, voici une implémentation en Clojure:
etc.
Comme l0st3d suggéré, pensez à utiliser se reproduire ou paresseux-seq.
Aussi, essayez de faire de votre séquence paresseux, en le construisant à l'aide de l'séquence de formes comme une opposition à le faire directement.
Voici un exemple d'utilisation de l'intégré dans la séquence de formes pour créer un paresseux suite de Fibonacci (à partir de la Programmation Clojure livre):
La pile de la profondeur est un petit ennui (configurable encore), mais même dans une langue avec de la queue de la récursivité comme Régime ou F# vous éventuellement d'exécuter hors de l'espace de pile avec votre code.
Aussi loin que je peux dire, votre code est peu probable que la queue de la récursivité optimisés, même dans un environnement qui prend en charge la récursion sur la queue de manière transparente. Vous voulez regarder une continuation passing style pour minimiser la profondeur de la pile.
Voici un exemple canonique dans le Schéma de Wikipédia, qui pourrait être traduit à Clojure, F# ou un autre langage fonctionnel sans trop de difficulté:
Un autre, récursive simple mise en œuvre simple pourrait être ceci:
À ajouter à Siddhartha Reddy réponse, vous pouvez également emprunter la fonction Factorielle forme La Structure Et l'Interprétation des Programmes d'Ordinateur, avec quelques Clojure-réglages spécifiques. Cela m'a donné une assez bonne performance, même pour de très grandes factorielle des calculs.
Factorielle des nombres sont de par leur nature, très grand. Je ne suis pas sûr de savoir comment Clojure traite avec cette (mais je vois qu'il fonctionne avec java), mais toute application qui n'utilise pas les chiffres de dépassement de très vite.
Edit: C'est sans prendre en considération le fait que vous êtes en utilisant la récursivité pour ce, qui est également susceptible d'utiliser des ressources.
Modifier x2: Si la mise en œuvre est à l'aide de grands nombres, qui, autant que je sache, sont généralement des tableaux, couplé avec la récursivité (un grand nombre de copie par l'entrée de la fonction, toujours sauvegardés sur la pile en raison de la fonction d'appels) pourrait expliquer un débordement de pile. Essayez de le faire dans une boucle for pour voir si c'est bien le problème.
;; Clojure 1.5.0-beta2 => (fact 400) ArithmeticException integer overflow clojure.lang.Numbers.throwIntOverflow (Numbers.java:1388)
*'
au lieu de*
il ne jette pas de débordement, mais de manière dynamique utilise nécessaire type.