在Lisp中创建与incf等效的宏功能

Jon*_*h P 1 lisp macros common-lisp

我刚刚开始学习宏功能的概念。

我的老师要求我们创建一个宏函数,该函数的功能与incf

这是他给我们流行的一个例子

(defmacro mypop (nom) 
   (list 'prog1 (list 'car nom) (list 'setq nom (list 'cdr nom))) )
Run Code Online (Sandbox Code Playgroud)

这是我要变成宏的常规函数​​:

(defun iincf (elem &optional num )
   (cond
      ((not num) (setq elem (+ 1 elem)))
      (t (setq elem (+ num elem))) ) )
Run Code Online (Sandbox Code Playgroud)

这是我尝试将其转换为宏的尝试:

(defmacro myincf (elem &optional num )
   (list 'cond
      ((list 'not num) (list 'setq elem (list '+ 1 elem)))
      (t (list 'setq elem (list '+ num elem))) ) )
Run Code Online (Sandbox Code Playgroud)

但是,出现此错误,我不知道为什么:

*** - system::%expand-form: (list 'not num) should be a lambda expression
Run Code Online (Sandbox Code Playgroud)

另外,我不确定我的函数是否会实际更改顶级变量的值。

所以这是我的两个问题:

  1. 为什么会出现此错误?
  2. 我要变成宏的函数是否正常?(如果成功地将其转换为宏函数,它将执行我打算做的事情吗?)

PS:我知道此练习可能会违反Lisp的许多通用规则,但这只是为了练习。谢谢!:)

jki*_*ski 5

错误的原因是您的语法无效:

((list ...) ...)
(t (list ...))
Run Code Online (Sandbox Code Playgroud)

第一个元素应该是函数名称或lambda表达式,因此您需要将其更改为类似

(list (list ...) ...)
(list t (list ...))
Run Code Online (Sandbox Code Playgroud)

尽管该宏不是一个很好的宏。首先,反引号语法将使代码更具可读性。它允许您编写仅评估指定表单的模板。例如,给定的MYPOP宏看起来像

(defmacro mypop (nom)
  `(prog1 (car ,nom)
     (setq ,nom (cdr ,nom))))
Run Code Online (Sandbox Code Playgroud)

仅对带有逗号的表单进行评估。与您的宏相同:

(defmacro myincf (elem &optional num)
  `(cond
     ((not ,num) (setq ,elem (+ 1 ,elem)))
     (t (setq ,elem (+ ,num ,elem)))))
Run Code Online (Sandbox Code Playgroud)

COND不是真的应该扩大,虽然一部分。应该在宏扩展期间对其进行评估,并且仅SETQ返回其中一个分支的表单。

(defmacro myincf (elem &optional num)
  (cond
    ((not num) `(setq ,elem (+ 1 ,elem)))
    (t `(setq ,elem (+ ,num ,elem)))))
Run Code Online (Sandbox Code Playgroud)

这两个分支之间的唯一区别是第一个默认为1for NUM。实现此目的的一种更简单的方法是提供NUM默认值。

(defmacro myincf (elem &optional (num 1))
  `(setq ,elem (+ ,num ,elem)))
Run Code Online (Sandbox Code Playgroud)

当然,该标准INCF稍微复杂一点,因为它适用于各种场所(不仅限于变量),并确保场所的子表单仅被评估一次。但是,由于该MYPOP示例无法处理这些问题,因此我也不认为您也必须这样做。

如果愿意,定义这种宏的简单方法是

(define-modify-macro myincf (&optional (num 1)) +)
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用类似的方法手动执行相同操作

(defmacro myincf (place &optional (num 1) &environment env)
  (multiple-value-bind (dummies vals store setter getter)
      (get-setf-expansion place env)
    `(let* (,@(mapcar #'list dummies vals)
            (,(first store) (+ ,getter ,num)))
       ,setter)))
Run Code Online (Sandbox Code Playgroud)

但是DEFINE-MODIFY-MACRO,在实际程序中最好使用它(缩短代码,减少错误)。你可以阅读GET-SETF-EXPANSIONDEFINE-MODIFY-MACRO如果你有兴趣。