Depende de cuán estrictamente defina "recursividad".
Si estrictamente requerimos que involucre la pila de llamadas (o cualquier mecanismo para mantener el estado del programa que se use), entonces siempre podemos reemplazarlo por algo que no lo haga. De hecho, los lenguajes que conducen naturalmente al uso intensivo de la recursividad tienden a tener compiladores que hacen un uso intensivo de la optimización de las llamadas de cola, por lo que lo que escribe es recursivo pero lo que ejecuta es iterativo.
Pero consideremos un caso en el que hacemos una llamada recursiva y usamos el resultado de una llamada recursiva para esa llamada recursiva.
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
if (m == 0)
return n+1;
if (n == 0)
return Ackermann(m - 1, 1);
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
Hacer que la primera llamada recursiva sea iterativa es fácil:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
restart:
if (m == 0)
return n+1;
if (n == 0)
{
m--;
n = 1;
goto restart;
}
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
Luego podemos limpiar y quitar el goto
para alejar a los velociraptores y la sombra de Dijkstra:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
while(m != 0)
{
if (n == 0)
{
m--;
n = 1;
}
else
return Ackermann(m - 1, Ackermann(m, n - 1));
}
return n+1;
}
Pero para eliminar las otras llamadas recursivas tendremos que almacenar los valores de algunas llamadas en una pila:
public static BigInteger Ackermann(BigInteger m, BigInteger n)
{
Stack<BigInteger> stack = new Stack<BigInteger>();
stack.Push(m);
while(stack.Count != 0)
{
m = stack.Pop();
if(m == 0)
n = n + 1;
else if(n == 0)
{
stack.Push(m - 1);
n = 1;
}
else
{
stack.Push(m - 1);
stack.Push(m);
--n;
}
}
return n;
}
Ahora, cuando consideramos el código fuente, ciertamente hemos convertido nuestro método recursivo en uno iterativo.
Teniendo en cuenta para qué se ha compilado, hemos convertido el código que usa la pila de llamadas para implementar la recursión en un código que no lo hace (y al hacerlo, el código convertido que arrojará una excepción de desbordamiento de la pila incluso para valores bastante pequeños en el código que simplemente tomar un tiempo insoportablemente largo para regresar [consulte ¿Cómo puedo evitar que mi función Ackerman desborde la pila? para obtener más optimizaciones que hacen que realmente regrese para muchas más entradas posibles]).
Teniendo en cuenta cómo se implementa generalmente la recursividad, hemos convertido el código que usa la pila de llamadas en código que usa una pila diferente para contener las operaciones pendientes. Por lo tanto, podríamos argumentar que todavía es recursivo, cuando se considera en ese nivel bajo.
Y a ese nivel, de hecho no hay otras formas de evitarlo. Entonces, si considera que ese método es recursivo, entonces hay cosas que no podemos hacer sin él. Generalmente, aunque no etiquetamos dicho código de forma recursiva. El término recursividad es útil porque cubre un cierto conjunto de enfoques y nos da una manera de hablar sobre ellos, y ya no estamos usando uno de ellos.
Por supuesto, todo esto supone que tienes una opción. Hay dos idiomas que prohíben las llamadas recursivas, y los idiomas que carecen de las estructuras de bucle necesarias para iterar.