首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >计算传递给在lisp中生成函数的宏的参数。

计算传递给在lisp中生成函数的宏的参数。
EN

Stack Overflow用户
提问于 2020-07-24 22:30:38
回答 3查看 204关注 0票数 0

我试图根据py-configparser创建的config对象为每个配置定义一个访问器函数的宏

代码语言:javascript
运行
复制
(defmacro make-config-accessor (config section option)
  ; create an upper case function name then intern
  (let* ((fun-name (intern (string-upcase
                             (str:replace-all "_" "-"
                                              (str:concat "get-" option)))))) 
    `(defun ,fun-name (config)
       (py-configparser:get-option config ,section ,option))))

如果将option作为字符串传递给它,但当它是像(car ("db" . "test.db"))这样的对时,表单就会按原样传递并导致错误。如何在不使用option的情况下计算宏中的eval参数。

完整示例:假设我有一个test.ini文件:

代码语言:javascript
运行
复制
[Settings]
db = "test.db"

使用py-configparser (您可以使用(ql:quickload "py-configparser")安装它),您可以将配置文件转换为Lisp对象:

代码语言:javascript
运行
复制
(setf *test-config* (py-configparser:make-config))
(py-configparser:read-files *test-config* '("~/test.ini"))

这应该是输出:

代码语言:javascript
运行
复制
#S(PY-CONFIGPARSER:CONFIG
   :DEFAULTS #S(PY-CONFIGPARSER::SECTION :NAME "DEFAULT" :OPTIONS NIL)
   :SECTIONS (#S(PY-CONFIGPARSER::SECTION
                 :NAME "Settings"
                 :OPTIONS (("db" . "\"test.db\""))))
   :OPTION-NAME-TRANSFORM-FN #<FUNCTION STRING-DOWNCASE>
   :SECTION-NAME-TRANSFORM-FN #<FUNCTION IDENTITY>)
("~/test.ini")

然后,您可以像这样检索db选项:

代码语言:javascript
运行
复制
(py-configparser:get-option *test-config* "Settings" "db")

产出:

代码语言:javascript
运行
复制
"\"test.db\""

现在,我正在编写一个宏,为每个选项(如db )创建一个函数,比如(get-db *test-config*)应该为我提供相同的输出。

我让它使用上面的make-config-accessor宏,但是当我传递一个像(car ("db" . "test.db"))这样的表单时,我必须使用eval,否则str:concat会失败。

我创建了一个gen-accessors,它遍历配置对象中的每个选项,并为它生成一个访问器:

代码语言:javascript
运行
复制
(defun gen-accessors (config)
  (let ((sections (py-configparser:sections config)))
    (loop for s in sections
       do (loop for i in (py-configparser:items config s)
             do (let* ((o (car i)))
                  (make-config-accessor config s o))))))
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-07-25 16:23:21

编写宏的第一条规则是:如果您发现自己正在使用eval,那么您几乎肯定犯了一个错误。在这种情况下,您所犯的错误是根本不需要宏:您需要一个函数。

特别是,您可能想要这个函数或类似的东西:

代码语言:javascript
运行
复制
(defun make-config-accessor (section option)
  ;; Make an accessor for OPTION in SECTION with a suitable name
  (let ((fun-name (intern (nsubstitute #\- #\_
                                       (format nil "GET-~A"
                                               (string-upcase option))))))
    (setf (symbol-function fun-name)
          (lambda (config)
            (py-configparser:get-option config section option)))
    fun-name)))

然后给出一个合适的配置读取器

代码语言:javascript
运行
复制
(defun read-config (&rest files)
  (py-configparser:read-files (py-configparser:make-config)
                              files))

与您的gen-accessors的一个相当简化(不太单一用途的绑定)版本一起使用。

代码语言:javascript
运行
复制
(defun gen-accessors (config)
  (loop for s in (py-configparser:sections config)
        appending (loop for i in (py-configparser:items config s)
                        collect (make-config-accessor s (car i)))))

然后,例如,如果/tmp/x.ini包含

代码语言:javascript
运行
复制
[Settings]
db = "test.db"
scrunge = 12

然后

代码语言:javascript
运行
复制
 > (gen-accessors (read-config "/tmp/x.ini"))
(get-scrunge get-db)

> (get-scrunge (read-config "/tmp/x.ini"))
"12"

您可以使用这样的方法使make-config-accessor的定义更好:

代码语言:javascript
运行
复制
(defun curryr (f &rest trailing-args)
  (lambda (&rest args)
    (declare (dynamic-extent args))
    (apply f (append args trailing-args))))

(defun make-config-accessor (section option)
  ;; Make an accessor for OPTION in SECTION with a suitable name
  (let ((fun-name (intern (nsubstitute #\- #\_
                                       (format nil "GET-~A"
                                               (string-upcase option))))))
    (setf (symbol-function fun-name)
          (curryr #'py-configparser:get-option section option))
    fun-name))

当然,并不是每个人都会觉得这样更好。

票数 2
EN

Stack Overflow用户

发布于 2020-07-25 04:16:32

这是一种罕见的情况,在这种情况下,您必须将eval与引用过的宏调用结合使用,并取消引用参数。

(我有一次被这个构造绊倒了,自己叫它eval-over-macro-call。-遵循命名传统let-over-lambda。-实际上它应该命名为eval-over-backquoted-macro-call-with-unquoting。它允许您动态使用宏。Vsevolod Dyomkin也是被它绊倒,独立。我回答了他,因为我是在同一时间或之前偶然发现的。宏--正如您意识到的--不允许对评估进行任意控制。)

但是首先,我生成了一些帮助函数。(您可以使用:str包函数,但我在安装它时遇到了问题。较少的依赖关系更好。就我个人而言,我更喜欢cl-ppcre来代替其他的东西。但是,在你的例子中,你可以摆脱任何依赖关系。

intern会污染您的命名空间。您只希望函数名称空间具有get-函数名条目。但不是变量命名空间。因此,若要只返回符号而不自动执行这些符号,请使用read-from-string

dotted-list-p函数需要:alexandria包。然而,一个人无论如何都需要它,因为它是最常用的lisp软件包之一(和:cl-ppcre一起),我认为这不算“附加依赖”。

对于dotted-pair-p函数,我不得不进行一些搜索。

dotted-list-to-list转换函数,我自己写的。

如果要为dotted-list使用简单的字符串列表,则可以去掉所有的options函数。

在这种情况下,在宏中,只需使用listp而不是dotted-list-p。使用option而不是(dotted-list-to-list option)

代码语言:javascript
运行
复制
;; one character replacement
(substitute #\+ #\Space "a simple example")
            replacer find obj

(defun string-to-upper-symbol (str)
  (read-from-string (substitute #\- #\_ (format nil "get-~A" str))))

(ql:quickload :alexandria)

(defun dotted-list-p (x)
  (and (not (alexandria:proper-list-p x))
       (consp x)))
;; correct - but gives nil if empty list (or (null x) ...) would include empty list

(defun dotted-or-empty-list-p (x)
  (or (null x) (dotted-list-p x)))
;; this gives t for empty list and dotted lists

(defun dotted-pair-p (x)
  (and (not (listp (cdr x))) (consp x)))

(defun dotted-list-to-list (dotted-list &optional (acc '()))
  (cond ((null dotted-list) (nreverse acc))
        ((dotted-pair-p dotted-list) (dotted-list-to-list '() (cons (cdr dotted-list) 
                                                                    (cons (car dotted-list) 
                                                                          acc))))
        (t (dotted-list-to-list (cdr dotted-list) (cons (car dotted-list) acc)))))

您的宏包含在参数列表config中,但是它从未使用过。

如果您忘了取消引用宏中的config,正确的解决方案是:

代码语言:javascript
运行
复制
(defmacro %make-config-accessor (config section option)
  ; create an upper case function name then intern
  (let* ((fun-name (string-to-upper-symbol option)))
    `(defun ,fun-name (,config)
       (py-configparser:get-option ,config ,section ,option)))))

(defun make-config-accessor (config section option)
  (if (dotted-list-p option)
      (loop for x in (dotted-list-to-list option)
            do (eval `(%make-config-accessor ,config ,section ,x)))
      (%make-config-accessor config section option)))

;; call with
;; (make-config-accessor '<your-config> '<your-section> '("option1" "option2" . "option3"))
;; test for existence
;; #'get-option1
;; #'get-option2
;; #'get-option3

在另一种情况下,您不需要配置,正确的解决方案是:

代码语言:javascript
运行
复制
(defmacro %make-config-accessor (section option)
  ; create an upper case function name then intern
  (let* ((fun-name (string-to-upper-symbol option)))
    `(defun ,fun-name (config)
       (py-configparser:get-option config ,section ,option)))))

(defun make-config-accessor (section option)
  (if (dotted-list-p option)
      (loop for x in (dotted-list-to-list option)
            do (eval `(%make-config-accessor ,section ,x)))
      (%make-config-accessor section option)))

;; call with
;; (make-config-accessor '<your-section> '("option1" "option2" . "option3"))
;; test for existence
;; #'get-option1
;; #'get-option2
;; #'get-option3

注意,由于您需要一个函数,所以您必须在调用参数configsection时引用它们(它们等待计算,而在函数中,option被求值。

感谢quotebackquote以及unquoteeval,您可以完全控制lisp中的评估级别。

有时,如果想要控制几轮评估,就必须在参数列表中使用更多的quote

您还可以将助手宏和函数合并到一个宏中。但是,每次调用宏时,都必须使用这个eval-over-backquoted-macro-call取消引用所需的参数。

代码语言:javascript
运行
复制
(defmacro make-config-accessor (section option)
  (if (dotted-list-p option)
      (loop for x in (dotted-list-to-list option)
            do (eval `(make-config-accessor ,section ,x)))
      `(defun ,(string-to-upper-symbol c) (config)
         (py-configparser:get-option config ,section ,option))))

;; call it with
;; (eval `(make-config-accessor <your-section> ,<your-option>))
;; e.g.
;; (eval `(make-config-accessor <your-section> ,'("opt1" "opt2" . "opt3")))
;; test existence with
;; #'get-opt1
;; #'get-opt2
;; #'get-opt3

顺便说一句。我不再相信这个"eval是被禁止的“说话了。在这样的情况下--大多数是宏中的评估控件,只有eval才能为这个问题编写额外的迷你解释器.这将是更繁琐(也很可能也更容易出错)。

你没有给出可行的代码。所以我不得不用一些复杂的函数/宏来解决所有这些问题,我写道。

代码语言:javascript
运行
复制
(defmacro q (b c)
  `(defun ,(string-to-upper-symbol c) (a) (list a ,b ,c)))

(defun q-fun (b c)
  (if (dotted-list-p c)
      (loop for x in (dotted-list-to-list c)
            do (eval `(q ,b ,x)))
      (q b c)))

;; (q "b" "c")
;; (q "b" '("d" . "e"))
;; (macroexpand-1 '(q "b" '("d" . "e")))

(defmacro p (b c)
  (if (dotted-list-p c)
      (loop for x in (dotted-list-to-list c)
            do (eval `(p ,b ,x)))
      `(defun ,(string-to-upper-symbol c) (a) (list a ,b ,c))))
票数 2
EN

Stack Overflow用户

发布于 2020-07-25 11:06:33

你需要两个层次的评估。

尝试:

代码语言:javascript
运行
复制
(defmacro make-config-accessor (config section option)
  ; create an upper case function name then intern
  `(let* ((fun-name (intern (string-upcase 
                            (str:replace-all "_" "-" (str:concat "get-" ,option)))))) 
     (eval `(defun ,fun-name (config)
              (py-configparser:get-option config ,,section ,,option)))))

现在,option是以let*形式计算的。然后,需要使用defun计算返回的eval表单(它总是在全局范围、空词法环境或toplevel中)。

这就是正确运行您的代码所需的所有更改。作为参考,我添加了我在这里运行的全部代码(注意:gen-accessors中有一个变化,我认为您是要使用config而不是*config*)。

代码语言:javascript
运行
复制
(ql:quickload "str")
(ql:quickload "py-configparser")

(defmacro make-config-accessor (config section option)
  ; create an upper case function name then intern
  `(let* ((fun-name (intern (string-upcase 
                              (str:replace-all "_" "-" 
                                               (str:concat "get-" ,option)))))) 
     (eval `(defun ,fun-name (config)
              (py-configparser:get-option config ,,section ,,option)))))

(defun gen-accessors (config)
  (let ((sections (py-configparser:sections config)))
    (loop for s in sections
          do (loop for i in (py-configparser:items config s)
                   do (let* ((o (car i)))
                        (make-config-accessor config s o))))))

(setf *test-config* (py-configparser:make-config))
(py-configparser:read-files *test-config* '("~/Desktop/test.ini"))
(gen-accessors *test-config*)

(get-db *test-config*)
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63082247

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档