recursion - Clojure: sólo puede recurrir desde la posición de la cola
(3)
El error Can only recur from tail position
significa que no está llamando a recur
como la última expresión en la parte recursiva de la función; de hecho, en su conj
código está la última expresión.
Algunas mejoras para hacer que su código funcione:
- Pregunte si la colección está vacía como el caso base, en lugar de comparar si su longitud es menor que dos
-
conj
recibe una colección para su primer parámetro, no un elemento - Es una mejor idea usar
cons
lugar deconj
(lo que agrega nuevos elementos en diferentes lugares según el tipo concreto de la colección, según la documentation ). De esta manera, la colección devuelta se invertirá si la colección de entrada es una lista o un vector (aunque el tipo de la colección devuelta siempre seráclojure.lang.Cons
, sin importar el tipo de la colección de entrada) - Tenga en cuenta que
''(coll)
es una lista con un solo elemento (el símbolocoll
) y no la colección real - Para revertir correctamente una lista necesita iterar sobre la lista de entrada y adjuntar cada elemento al principio de una lista de salida; usar un parametro acumulador para esto
- Para aprovechar la recursión de cola, la llamada se
recur
en la posición de cola de la función; de esta manera, cada invocación recursiva ocupa una cantidad constante de espacio y la pila no crecerá ilimitada
Creo que esto es lo que buscabas:
(defn recursive-reverse [coll]
(loop [coll coll
acc (empty coll)]
(if (empty? coll)
acc
(recur (rest coll) (cons (first coll) acc)))))
Estoy tratando de revertir recursivamente una lista, pero estoy obteniendo. Can only recur from tail position
al ejecutar. ¿Qué significa esto exactamente y cómo puede mejorarse mi código para que funcione?
(defn recursive-reverse [coll]
(loop [coll coll]
(if (< (count coll) 2) ''(coll)
(conj (first coll) (recur (rest coll)))
)))
EDITAR
Salida para la solución de Oscar. ¿Funciona para listas pero no para vectores?
user=> (= (recursive-reverse [1 2 3 4 5]) (recursive-reverse ''(1 2 3 4 5)))
false
user=> (= ''(1 2 3 4 5) [1 2 3 4 5])
true
user=> (recursive-reverse [1 2 3 4 5])
[1 2 3 4 5]
user=> (recursive-reverse ''(1 2 3 4 5))
(5 4 3 2 1)
La forma más fácil de hacer que su código funcione es usar el nombre de la función en lugar de recur
:
(defn recursive-reverse [coll]
(loop [coll coll]
(if (< (count coll) 2) ''(coll)
(conj (first coll) (recursive-reverse (rest coll)))
)))
Por supuesto, hará que la pila de llamadas sea enorme y que se produzcan errores en las entradas suficientemente grandes.
Aquí hay otra forma de escribir una versión de recursive-reversa amigable con la cola:
(defn recursive-reverse [s]
(letfn [
(my-reverse [s c]
(if (empty? s)
c
(recur (rest s) (conj c (first s)))))]
(my-reverse s ''())))
Extrae elementos del encabezado de la lista de entrada y los agrega al encabezado de la lista de acumuladores.
''Posición de la cola'' solo significa que la recur
es la última cosa que realiza la función antes de regresar. Esto difiere de su solución de ''recursión natural'' en la cual todavía se está trabajando por la función entre llamarse y regresar.
Solo puedes llamar recurrir desde la posición de cola en Clojure. Es parte del diseño del lenguaje y es una restricción relacionada con la JVM.
Puede llamar al nombre de su función (usando la recursividad) sin usar la repetición, y dependiendo de cómo estructure su programa, ya sea que use secuencias perezosas o no, es posible que no tenga la explosión de la pila. Pero es mejor usar Recur, y el uso de bucle con repetición te permite algunos enlaces locales.
Aquí hay un ejemplo de 4Clojure.com donde la recursión se usa sin recurrir.
(fn flt [coll]
(let [l (first coll) r (next coll)]
(concat
(if (sequential? l)
(flt l)
[l])
(when (sequential? r)
(flt r)))))