es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Definiendo una macro para iterar

Quería definir una nueva cláusula para la macro iterate. Algo similar a la función range de Python donde se tienen los parámetros start, stop, step. Aquí está el primer intento:

(defmacro-clause (for var start start stop stop step step)
  (if (minusp step)
      `(for ,var from ,start downto ,stop by (- ,step))
      `(for ,var from ,start to     ,stop by ,step)))

Se encarga de trabajar con rangos crecientes y decrecientes utilizando las palabras clave to y downto de iterate. (A diferencia de Python, estos incluyen el valor stop).

Esto funciona como se desea para:

(iter (for x start 5 stop 3 step -1)
      (collect x))

Resultado: (5 4 3)

(iter (for x start 2 stop 5 step 1)
      (collect x))

Resultado: (3, 4, 5)

Sin embargo, falla para algo como:

(let ((a 9)
      (b 3)
      (c -1))
  (iter (for x start a stop b step c)
        (collect x)))

¿Es una peculiaridad de iterate que requiere números explícitos en estos lugares? No tiene problema con cosas como:

(iter (for x below (+ 3 3) by (+ 1 1))
      (collect x))

Concretamente, mi pregunta es, ¿cómo puedo definir una nueva cláusula iterate que acepte variables asignadas a números en estos lugares?

Tags:  , ,

Answer

  1. Avatar for davy.ai

    Desafortunadamente, no es posible utilizar variables vinculadas a números en las posiciones de start, stop y step de la cláusula for en la macro iterate. Esto se debe a que esas posiciones requieren literales en tiempo de expansión de macros, y las variables no se conocen hasta el tiempo de ejecución. Por lo tanto, no es una particularidad de iterate, sino una limitación fundamental de las macros en general.

    Una posible solución alternativa es utilizar una macro separada para evaluar las variables y luego pasar sus valores a la macro iterate:

    (defmacro my-range (start stop step)
      `(loop for i from ,start to ,stop by ,step))
    
    (iter (for x in (my-range a b c))
          (collect x))
    

    Esto define una macro my-range que toma variables vinculadas a números y se expande en una forma loop que genera la secuencia deseada. Luego podemos llamar a my-range dentro de iterate para generar la secuencia e iterar sobre ella como de costumbre. Ten en cuenta que esta implementación asume que las variables están vinculadas a secuencias ascendentes; si necesitas manejar secuencias descendentes también, deberás modificar my-range en consecuencia.

    Otra posible solución alternativa es utilizar una función en lugar de una macro para la cláusula for:

    (defun my-for (var start stop step)
      (if (minusp step)
          `(for ,var from ,start downto ,stop by (- ,step))
          `(for ,var from ,start to     ,stop by ,step)))
    
    (iter (my-for x a b c)
          (collect x))
    

    Esto define una función my-for que toma variables vinculadas a números y devuelve la cláusula for correspondiente como una lista. Luego podemos llamar a my-for dentro de iterate para generar la cláusula for de manera dinámica en función de los valores de las variables. Este enfoque tiene la ventaja de ser más flexible que la macro my-range, ya que puede manejar todo tipo de cláusulas for (no sólo rangos). Sin embargo, tiene la desventaja de ser más lento que una macro, ya que implica llamadas de función en tiempo de ejecución y construcción de listas.

    En general, aunque no es posible definir una cláusula for que acepte variables vinculadas a números directamente en iterate, existen varias soluciones alternativas que pueden lograr efectos similares si es necesario.

Comments are closed.