L'augmentation de la taille de la pile servira seulement d'un pansement temporaire. Comme d'autres l'ont souligné, ce que vous voulez vraiment est la queue d'appel d'élimination, et Java n'a pas de ce, pour diverses raisons. Cependant, vous pouvez tricher si vous le souhaitez.
Pilule rouge dans la main? OK, cette manière s'il vous plaît.
Il ya des façons dont vous pouvez échanger la pile pour tas. Par exemple, au lieu de faire un appel récursif à l'intérieur d'une fonction, de retour d'un paresseux discbased qui fait de l'appel au moment de l'évaluation. Vous pourrez ensuite vous détendre la "pile" avec Java pour construire. Je vais le démontrer par un exemple. Considérer ce code Haskell:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = (f x) : map f xs
Notez que cette fonction ne jamais évalue la queue de la liste. Si la fonction n'a pas vraiment besoin de faire un appel récursif. En Haskell, il retourne en fait un thunk pour la queue, qui est appelé si c'est nécessaire. Nous pouvons faire la même chose en Java (ce qui utilise des classes à partir Fonctionnel Java):
public <B> Stream<B> map(final F<A, B> f, final Stream<A> as)
{return as.isEmpty() ? nil()
: cons(f.f(as.head()), new P1<Stream<A>>()
{public Stream<A> _1() {return map(f, as.tail);}});}
Notez que Stream<A>
consiste en une valeur de type A
et une valeur de type P1
qui est comme un thunk qui renvoie le reste du flux lorsque _1() est appelée. Bien qu'il ressemble certainement la récursivité, l'appel récursif de la carte n'est pas faite, mais devient une partie du Flux de discbased.
Cela peut ensuite être déroulés avec un régulier pour-construire.
for (Stream<B> b = bs; b.isNotEmpty(); b = b.tail()._1())
{System.out.println(b.head());}
Voici un autre exemple, puisque vous parlions du Projet Euler. Ce programme utilise les fonctions mutuellement récursives et ne souffle pas la pile, même pour des millions d'appels:
import fj.*; import fj.data.Natural;
import static fj.data.Enumerator.naturalEnumerator;
import static fj.data.Natural.*; import static fj.pre.Ord.naturalOrd;
import fj.data.Stream; import fj.data.vector.V2;
import static fj.data.Stream.*; import static fj.pre.Show.*;
public class Primes
{public static Stream<Natural> primes()
{return cons(natural(2).some(), new P1<Stream<Natural>>()
{public Stream<Natural> _1()
{return forever(naturalEnumerator, natural(3).some(), 2)
.filter(new F<Natural, Boolean>()
{public Boolean f(final Natural n)
{return primeFactors(n).length() == 1;}});}});}
public static Stream<Natural> primeFactors(final Natural n)
{return factor(n, natural(2).some(), primes().tail());}
public static Stream<Natural> factor(final Natural n, final Natural p,
final P1<Stream<Natural>> ps)
{for (Stream<Natural> ns = cons(p, ps); true; ns = ns.tail()._1())
{final Natural h = ns.head();
final P1<Stream<Natural>> t = ns.tail();
if (naturalOrd.isGreaterThan(h.multiply(h), n))
return single(n);
else {final V2<Natural> dm = n.divmod(h);
if (naturalOrd.eq(dm._2(), ZERO))
return cons(h, new P1<Stream<Natural>>()
{public Stream<Natural> _1()
{return factor(dm._1(), h, t);}});}}}
public static void main(final String[] a)
{streamShow(naturalShow).println(primes().takeWhile
(naturalOrd.isLessThan(natural(Long.valueOf(a[0])).some())));}}
Une autre chose que vous pouvez faire pour l'échange de la pile pour le segment est d'utiliser plusieurs threads. L'idée est qu'au lieu de faire un appel récursif, vous créez un thunk qui fait l'appel, à la main cette thunk un nouveau fil de discussion et de laisser le thread courant de sortie de la fonction.
Ce qui suit est un exemple de cela. Les excuses qu'il est un peu opaque à regarder sans le import static
clauses: voir ici en contexte
public static <A, B> Promise<B> foldRight(final Strategy<Unit> s,
final F<A, F<B, B>> f,
final B b,
final List<A> as)
{return as.isEmpty() ? promise(s, P.p(b))
: liftM2(f).f(promise(s, P.p(as.head())))
.f(join(s, new F<List<A>, P1<Promise<B>>>()
{public Promise<B> f(List<A> l)
{return foldRight(s, f, b, l);}}.f(as.tail())));}
Strategy<Unit> s
, soutenu par un pool de threads, et l' promise
fonction mains d'un thunk pour le pool de threads, de retour d'un Promise
, ce qui est très bien comme java.util.concurrent.Future
, mais en mieux. Voir ici. Le point est que la méthode ci-dessus les plis d'un droit récursive discbased à droite en O(1) de la pile, qui normalement nécessite la queue-appel d'élimination. Donc, nous avons effectivement reçu le TCE, en échange d'une certaine complexité. Vous appelez cette fonction comme suit:
Strategy<Unit> s = Strategy.simpleThreadStrategy();
int x = foldRight(s, Integers.add, List.nil(), range(1, 10000)).claim();
System.out.println(x); // 49995000
Notez que cette dernière technique fonctionne parfaitement pour les non-linéaire de la récursivité. C'est, il sera exécuté en constante pile de même des algorithmes qui n'ont pas de queue appels.
Une autre chose que vous pouvez faire est d'employer une technique appelée le trampoline. Un trampoline est un calcul, réifiée comme une structure de données, qui peut être franchi. La Fonctionnelle de la bibliothèque Java comprend un Trampoline
type de données que j'ai écrit, ce qui vous permet de transformer n'importe quel appel de fonction dans une queue d'appel. Comme un exemple ici est un trampolined foldRightC
que les plis à droit constant de la pile:
public final <B> Trampoline<B> foldRightC(final F2<A, B, B> f, final B b) {
return Trampoline.suspend(new P1<Trampoline<B>>() {
public Trampoline<B> _1() {
return isEmpty() ? Trampoline.pure(b)
: tail().foldRightC(f, b).map(f.f(head()));}});}
C'est le même principe que l'utilisation de plusieurs threads, sauf qu'au lieu d'invoquer chaque étape dans son propre thread, nous construisons chaque étape sur le tas, très semblable à l'aide d'un Stream
, et puis nous avons l'exécution de toutes les étapes en une seule boucle avec Trampoline.run
.