定义一个用于迭代的宏。

3

我想为iterate宏定义一个新的子句,类似于Python中的range函数,其中包含start, stop, step参数。下面是第一次尝试:

(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)))

使用迭代的 todownto 关键字处理增加和减少的范围。(请注意,与 Python 不同,这些关键字包括 stop 值。)

对于以下内容,这样的操作能够正常运行

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

;; => (5 4 3)

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

;; => (3,4,5)

然而,它对像这样的任何东西都失败了

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

iterate需要在这些地方明确的数字,这是它的一个怪癖吗?但它对这样的事情没有问题:

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

具体来说,我的问题是如何定义一个新的iterate子句,该子句接受绑定到这些位置上的数字变量?


1
你的问题在于你在宏展开时使用了 step。如果它不是数字字面量,那么这根本不起作用,否则也不会按预期工作。 - Svante
谢谢您的回复@Svante,但我不清楚您的意思。为什么您认为该步骤必须是一个数字字面值而不是表达式呢?换句话说,宏展开的哪个方面使其不可接受? - user3414663
你正在宏展开时评估步骤的符号。实际编译代码仅具有一个扩展(正步长或负步长)。 - Svante
1个回答

2
问题在于您试图在宏展开时决定一些无法预知的事情,例如变量的符号。特别是,您不能编写一个宏,该宏会展开为(部分)另一个宏,具体取决于仅在运行时才知道的任何内容,或者您可以这样做,但这必然意味着您必须在运行时调用道德等价物eval,而...不要那样做。
相反,您必须在运行时决定计数方式。这意味着您不能使用任何(for var from ...) 或相关子句,因为似乎没有任何子句是关于方向不可知的(为什么(for i from 1 to -5 by -1)不起作用超出了我的理解范围,但...好吧)。
因此,无论您最终选择哪个子句,都需要将其展开为一个(for var next ...) 子句,我想。
以下是这样的尝试。免责声明:未经过多次测试,我不使用迭代,可能会在接触时爆炸,对鱼有毒。
(defmacro-driver (for v in-range a to b &optional by s)
  (let ((firstp (make-symbol "FIRSTP"))
        (value (make-symbol "VALUE"))
        (limit (make-symbol "LIMIT"))
        (step (make-symbol "STEP")))
    `(progn
       (with ,firstp = t)
       (with ,value = (let ((v ,a))
                        (unless (numberp v)
                          (warn "doomed"))
                        (when (null v)
                          (warn "extremely doomed"))
                        v))
       (with ,limit = (let ((v ,b))
                        (unless (numberp v)
                          (warn "also doomed"))
                        v))
       (with ,step = (let ((v (or ,s (signum (- ,limit ,value)))))
                       (when (not (numberp v))
                         (warn "doomed again"))
                       (when (zerop v)
                         (warn "zero step"))
                       (when (not (= (signum v) (signum (- ,limit ,value))))
                         (warn "likely doomed"))
                       v))
       (,(if generate 'generate 'for)
        ,v
        next (if ,firstp
                 (progn
                   (setf ,firstp nil)
                   ,value)
               (progn
                 (incf ,value ,step)
                 (when (if (> ,step 0)
                           (>= ,value ,limit)
                         (<= ,value ,limit))
                   (terminate))
                 ,value))))))

现在

> (iter (for i in-range 1 to 5 by 2)
    (print i))

1 
3
nil

> (iter (for i in-range 1 to -1)
    (print i))

1
0
nil

> (iter (for i in-range 1 to 5 by -2)
    (when (< i -20)
      (terminate)))
Warning: likely doomed
nil

显然,其中一些检查可以更好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接