宏中的动态lambda参数列表

发布于 2025-02-12 08:34:13 字数 1931 浏览 0 评论 0原文

背景

我通常要撰写多个函数调用,第一个函数要求将lambda函数作为参数传递。我想将最常见的用例包装在宏中,在动态创建Lambda功能的Lambda列表方面遇到麻烦。有一个以前的讨论< /a>,但在这种情况下没有任何帮助。

目的是像这个示例一样调用宏: (过滤器数据(and(String = Origin“ USA”))(&GT;加速10)) 其中Origin加速是数据中的列。

当前的宏

(defmacro filter (data &body body)
  (with-unique-names (variables predicate)
   `(let* ((,variables (key-list ,data ',@body))
1.         (,predicate (lambda ,variables ,@body)) ;not working
2.      ;; (,predicate (lambda (origin acceleration) ,@body)) ; works
3.         (,predicate (eval (read-from-string
                               (format nil "#.(lambda ~A ~A)" 
                                            ,variables ',@body)))
      (choose ,data
              (mask ,data ,variables ,predicate)
              t))))

在此示例中,键列表是一个函数获取数据和主体,并产生包含谓词中所有变量的列表。例如:

CL-USER> (key-list vgcars '(and (string= origin "USA") (> acceleration 10)))
(ORIGIN ACCELERATION)

我已经确认它是列表,并且正确。

中让表格:

  1. 应该 work
  2. 工作,但要求我手工编码lambda的参数列表,这将是不同的对于每个谓词查询。

执行宏会产生编译时错误,以执行宏观错误:

Compile-time error:
  The lambda expression has a missing or non-list lambda list:
  (LAMBDA #:VARIABLES1199 (AND (STRING= ORIGIN "USA") (> ACCELERATION 10)))

请注意,,变量>的替换缺乏替代。我已经打印并键入检查,变量,它 is 具有正确值的列表。同样,bask要求变量以类似的方式传递,并且在lambda函数的硬编码版本中,此替代起作用。我尝试了参数列表的各种变体,例如(,@variables)等,但无效。

该问题似乎是特定于通过宏替换创建Lambda函数的参数列表。

有人有什么想法吗?

编辑 基于评论,我使用eval添加了版本3。这确实有效,但需要自动逃脱报价。评估这样的代码存在风险,但是由于打算从repl中调用,因此没有比已经存在的风险更大。

Background

I'm typically composing several function calls, the first of which requires a lambda function to be passed as an argument. I want to wrap the most common use cases with a macro, am having trouble with dynamically creating the lambda list for the lambda function. There was one previous discussion, but nothing there helps in this situation.

The intention is to call the macro like this example:
(filter data (and (string= origin "USA") (> acceleration 10)))
where origin and acceleration are columns in the data.

Current macro

(defmacro filter (data &body body)
  (with-unique-names (variables predicate)
   `(let* ((,variables (key-list ,data ',@body))
1.         (,predicate (lambda ,variables ,@body)) ;not working
2.      ;; (,predicate (lambda (origin acceleration) ,@body)) ; works
3.         (,predicate (eval (read-from-string
                               (format nil "#.(lambda ~A ~A)" 
                                            ,variables ',@body)))
      (choose ,data
              (mask ,data ,variables ,predicate)
              t))))

In this example, key-list is a function takes the data and body and produces a list containing all the variables in the predicate. For example:

CL-USER> (key-list vgcars '(and (string= origin "USA") (> acceleration 10)))
(ORIGIN ACCELERATION)

I've confirmed that it is a list, and correct.

In the let form:

  1. is what should work
  2. does work, but requires me to hand-code the lambda's parameter list, and this will be different for every predicate query.

Executing the macro as it is produces a compile time error:

Compile-time error:
  The lambda expression has a missing or non-list lambda list:
  (LAMBDA #:VARIABLES1199 (AND (STRING= ORIGIN "USA") (> ACCELERATION 10)))

Note the lack of substitution for ,variables. I've printed and type checked ,variables and it is a list with the correct values. As well, mask requires the variables to be passed in a similar manner, and in the hard-coded version of the lambda function this substitution works. I've tried all kinds of variations for the parameter list, e.g. (,@variables), etc., but none work.

The problem seems to be specific to creating the parameter list of the lambda function via macro substitution.

Anyone have any ideas?

Edit
Based on a comment, I've added a version 3, using eval. This does work but requires escaping quote automatically. There is a risk in evaluating code like this, but since it's intended to be invoked from the REPL, no more risky than what already exists.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

放赐 2025-02-19 08:34:13

编写宏时需要应用一些规则。这里提到的一些人:

  1. 了解宏是代码生成器,并且看不到运行时数据

  2. 没有像函数那样命名宏。宏应以不同的方式命名。功能名称处于活动状态:打印,附加,过滤器,...宏名称应使用过滤器,过滤,...

  3. 内部的宏定义清楚地说明了宏的变量是什么。不要使用变量,而是变量符号。变量的值不是变量,而是符号。使用不同的命名来用于变量和宏扩展阶段的功能。

  4. 清楚地分开并记录宏扩展阶段的变量和功能。

  5. 明确分开生成代码和/或返回的代码

  6. 用宏expand和macroexpand -1-&gt;检查宏扩展

检查现在查看您的第一个版本的

(defmacro filter (data &body body)
  (with-unique-names (variables predicate)

   ; here VARIABLES is FOO100
   ; here PREDICATE is FOO200
  
   ; now you want to return a computed list via a template

   `(let* ((,variables          ; <- FOO100

           (key-list ,data ',@body))   ; <- this is not evaluated, but a list is constructed

           ;  (key-list <value of DATA> '<spliced in value of BODY>)

           (,predicate          ; <- FOO200

            (lambda ,variables ,@body))   ; <- this is not evaluated, but a list is constructed

           ; (lambda FOO100 <spliced in value of BODY>)

           ; as you can see above makes no sense, since LAMBDA
           ; needs a list of variables, not a symbol.
           ; this its an invalid lambda list

:)

      (choose ,data
              (mask ,data ,variables ,predicate)
              t))))

现在是另一个变体

(defmacro filter (data &body body)
  (with-unique-names (variables predicate)

   ; here VARIABLES is FOO100
   ; here PREDICATE is FOO200
  
   ; now you want to return a computed list via a template

   `(let* ((,variables          ; <- FOO100

           (key-list ,data ',@body))   ; <- this is not evaluated,
                                       ; but a list is constructed

           ;  (key-list <value of DATA> '<spliced in value of BODY>)

           (,predicate          ; <- FOO200

            (eval (read-from-string
                               (format nil "#.(lambda ~A ~A)" 
                                            ,variables ',@body)))
            ; <- above is not evaluated, but a list is constructed

           ; (EVAL (READ-FROM-STRING
           ;         (FORMAT NIL "#.(lambda ~A ~A)
           ;                 FOO100 '<spliced in value of BODY)))

           ; Above then will run at runtime and create a function via EVAL.
           ; this is especially bad because you print code,
           ; read it and then evaluate it
           ; to generate a function -> very ugly 

:)

      (choose ,data
              (mask ,data ,variables ,predicate)
              t))))


(defmacro filter (data &body body)
  (with-unique-names (variables predicate)
   `(let* ((,variables (key-list ,data ',@body))
1.         (,predicate (lambda ,variables ,@body)) ;not working
2.      ;; (,predicate (lambda (origin acceleration) ,@body)) ; works
3.         (,predicate (eval (read-from-string
                               (format nil "#.(lambda ~A ~A)" 
                                            ,variables ',@body)))
      (choose ,data
              (mask ,data ,variables ,predicate)
              t))))

,然后呼叫:

(filter data (and (string= origin "USA") (> acceleration 10)))

当宏运行时:

数据是data body

body as (and(string = Origin) ”)(&gt;加速10))

变量是foo100

prepicates is foo200

然后您返回回语列表。回调的列表包含静态数据和数据的组合,这些数据和数据在宏扩展时间进行了评估。在宏膨胀时间评估了前面逗号的每个表达式, rets在宏扩展时间均未评估

不在宏扩展时间运行:

(KEY-LIST DATA '(and (string= origin "USA") (> acceleration 10))

不在宏扩展时间运行:

(eval ...)

这意味着您有很多混乱:

  1. 通过with-uinque-names在宏扩展时间引入的新名称

  2. 在宏扩展时间运行的一些代码在宏扩展时间引入的新名称,而不是在其后面的系统

  3. 运行时评估,即使从文本中阅读

    也是如此

通常编写的宏:

(defmacro filtering (data &body body)

   (let* ((variable-names (key-list data body))

          (predicate-expression

               `(lambda ,variable-names ,@body)))

       ; The following is the template for the expression to generate
      
       `(choose ,data
                (mask ,data
                      ,variable-names
                      ,predicate-expression)
                t)))

这就是这样的:这是这样的:

(pprint (macroexpand-1 '(filter data
                                (and (string= origin "USA")
                                     (> acceleration 10)))))

(CHOOSE
 DATA
 (MASK
  DATA
  (ORIGIN ACCELERATION)
  (LAMBDA (ORIGIN ACCELERATION)
     (AND (STRING= ORIGIN "USA")
          (> ACCELERATION 10))))
 T)

因为我现在没有掩码或选择的内容,这只是猜测如何为它们生成形式的猜测。

但最大的问题是:密钥列表是在宏扩展时间运行的

您确实需要思考这一点,为什么要通过宏生成代码以及如何生成代码。宏在编译时间运行,然后看不到运行时值。如果变量列表仅在运行时可用,则宏无法生成一个需要这些变量的函数的代码。然后,需要在运行时计算和编译该功能。

There are a few rules you need to apply when writing macros. Some mentioned here::

  1. understand that a macro is a code generator and does not see runtime data

  2. don't name a macro like a function. A macro should be named differently. Function names are active: print, append, filter, ... Macro names should be WITH-FILTER, FILTERING, ...

  3. inside a macro definition make clear what a variable of the macro is. Don't use VARIABLES, but VARIABLES-SYMBOL. The value of VARIABLES is not the VARIABLES, but a SYMBOL. Use different namings for variables and functions of the macro-expansion phase.

  4. clearly separate and DOCUMENT what are variables and functions of the macro expansion phase are.

  5. clearly separate where code is generated and/or returned

  6. test your macro with MACROEXPAND and MACROEXPAND-1 -> check the macro expansion

Now looking at your first version:

(defmacro filter (data &body body)
  (with-unique-names (variables predicate)

   ; here VARIABLES is FOO100
   ; here PREDICATE is FOO200
  
   ; now you want to return a computed list via a template

   `(let* ((,variables          ; <- FOO100

           (key-list ,data ',@body))   ; <- this is not evaluated, but a list is constructed

           ;  (key-list <value of DATA> '<spliced in value of BODY>)

           (,predicate          ; <- FOO200

            (lambda ,variables ,@body))   ; <- this is not evaluated, but a list is constructed

           ; (lambda FOO100 <spliced in value of BODY>)

           ; as you can see above makes no sense, since LAMBDA
           ; needs a list of variables, not a symbol.
           ; this its an invalid lambda list

)

      (choose ,data
              (mask ,data ,variables ,predicate)
              t))))

Now the other variant:

(defmacro filter (data &body body)
  (with-unique-names (variables predicate)

   ; here VARIABLES is FOO100
   ; here PREDICATE is FOO200
  
   ; now you want to return a computed list via a template

   `(let* ((,variables          ; <- FOO100

           (key-list ,data ',@body))   ; <- this is not evaluated,
                                       ; but a list is constructed

           ;  (key-list <value of DATA> '<spliced in value of BODY>)

           (,predicate          ; <- FOO200

            (eval (read-from-string
                               (format nil "#.(lambda ~A ~A)" 
                                            ,variables ',@body)))
            ; <- above is not evaluated, but a list is constructed

           ; (EVAL (READ-FROM-STRING
           ;         (FORMAT NIL "#.(lambda ~A ~A)
           ;                 FOO100 '<spliced in value of BODY)))

           ; Above then will run at runtime and create a function via EVAL.
           ; this is especially bad because you print code,
           ; read it and then evaluate it
           ; to generate a function -> very ugly 

)

      (choose ,data
              (mask ,data ,variables ,predicate)
              t))))


(defmacro filter (data &body body)
  (with-unique-names (variables predicate)
   `(let* ((,variables (key-list ,data ',@body))
1.         (,predicate (lambda ,variables ,@body)) ;not working
2.      ;; (,predicate (lambda (origin acceleration) ,@body)) ; works
3.         (,predicate (eval (read-from-string
                               (format nil "#.(lambda ~A ~A)" 
                                            ,variables ',@body)))
      (choose ,data
              (mask ,data ,variables ,predicate)
              t))))

And you call:

(filter data (and (string= origin "USA") (> acceleration 10)))

When the macro runs:

DATA is DATA

BODY is (AND (STRING= ORIGIN "USA") (> ACCELERATION 10))

VARIABLES is FOO100

PREDICATES is FOO200

Then you return a backquoted list. The backquoted list contains a mix of static data and data which is evaluated at macroexpansion time. Every expression with a comma in front is evaluated at macro expansion time, the rest is not evaluated at macro expansion time.

Not run at macro expansion time:

(KEY-LIST DATA '(and (string= origin "USA") (> acceleration 10))

Not run at macro expansion time:

(eval ...)

That means you have a lot of confusion:

  1. new names introduced at macro-expansion time via WITH-UINQUE-NAMES

  2. some code running at macro expansion time and some not, without systematic behind it

  3. runtime evaluation, even with reading from text

A macro typically written:

(defmacro filtering (data &body body)

   (let* ((variable-names (key-list data body))

          (predicate-expression

               `(lambda ,variable-names ,@body)))

       ; The following is the template for the expression to generate
      
       `(choose ,data
                (mask ,data
                      ,variable-names
                      ,predicate-expression)
                t)))

That would then look like this:

(pprint (macroexpand-1 '(filter data
                                (and (string= origin "USA")
                                     (> acceleration 10)))))

(CHOOSE
 DATA
 (MASK
  DATA
  (ORIGIN ACCELERATION)
  (LAMBDA (ORIGIN ACCELERATION)
     (AND (STRING= ORIGIN "USA")
          (> ACCELERATION 10))))
 T)

Since I don't now what MASK or CHOOSE does, this is just a guess how to generate forms for them.

BUT THE BIG PROBLEM: KEY-LIST is run at macro expansion time

You really need to think this through, why you want code to be generated by a macro and how. MACROS run at COMPILE TIME and then can't see RUNTIME values. If a list of variables is only available at runtime, then the macro can't generate the code for a function, which needs these variables. The function then needs to be computed and compiled at runtime.

烟─花易冷 2025-02-19 08:34:13

应该很明显,没有这样的事情可以起作用。更普遍地说,我认为您可能对宏的目的感到困惑:请参阅 Rainer Joswig的答案有关此指南的答案。

考虑到这种情况

(let ((d (read-data-from-file "my-data-file")))
  (filter d ...))

d在运行程序后才知道,因此,直到那个时候才知道d中的任何命名列。但是,如果filter是一个宏,则在此之前将其扩展,因此在此之前创建了lambda表达式表达式是在此之前创建的必须在此之前知道其论点。因此,宏不能知道数据中列的名称。

再次记住,通用LISP中的宏是其参数是源代码,其值为源代码。宏。在程序运行时仅知道的事物。

(当然,常见的LISP也是一种语言,可以为您提供eval&amp;相关的事物,您可以在脖子上吹腿,但不这样做 :为了重新利用JWZ的著名报价:

LISP程序员曾经有问题。他们认为,“我知道,我会使用eval'。现在他们有无限的问题。


答案是不使用eval ,而是重新考虑问题。例如:让我们假设您拥有的任何数据都在某种形式的编号列中使用的字段。也许有一个column-ref访问者。

拥有数据(SO:之后,宏观扩展时间)后,您可以从中计算出与列相对应的字段的名称。因此,例如,您可以构造某种表格从字段名称到列号的映射:

(defun compute-key-table (data)
  (let ((keytable (make-hash-table)))
    ;; keytable maps from named field to its position perhaps?
    ...
    keytable))

现在您可以编写键ref conscotsors:

(declaim (inline key-ref (setf key-ref))
(defun key-ref (data key keytable)
  ;; This does not check the key exists
  (column-ref data (gethash key keytable))

(defun (setf key-ref) (new data key keytable)
  (setf (column-ref data (gethash key keytable)) new)

现在,现在使用这些表格,您可以编写此宏:

(defmacro with-data-field-accessors (data (&rest fields) &body forms)
  ;; data is either a symbol naming the data variable, or (variable <form>)
  ;; fields are the fields we want accessors for
  (let ((<kt> (make-symbol "KT"))
        (<data> (etypecase data
                  (symbol data)
                  (cons (first data)))))
    `(let* (,@(etypecase data
                  (symbol '())
                  (cons `((,@data))))
            (,<kt> (compute-key-table ,<data>)))
       (dolist (field ',fields)
         (unless (gethash field ,<kt>)
           (error "no data field ~S" field)))
       (symbol-macrolet ,(mapcar (lambda (field)
                                   `(,field (key-ref ,<data> ',field)))
                                 fields)
         ,@forms))))

因此现在您可以使用以下内容:

(with-data-field-accessors (x (read-data)) (foo bar)
  (+ foo bar))

将其扩展到:

(let* ((x (read-data)) (#:kt (compute-key-table x)))
  (dolist (field '(foo bar))
    (unless (gethash field #:kt) (error "no data field ~S" field)))
  (symbol-macrolet ((foo (key-ref x 'foo)) (bar (key-ref x 'bar))) (+ foo bar)))

请注意,此宏:

  • IS 给定 ,并且不会试图从其无法拥有的信息中计算它们;
  • 扩展到代码,该代码将在运行时计算字段名称到柱数字映射,并将检查一次这些映射是否有效;
  • 使字段名称看起来像symbol-macrolet的变量,因为它被告知字段名称;
  • 不调用eval

显然,这太过了:大概数据有行之类的东西或其他所有内容是不值得的。无论答案是什么,都不是不是使用eval

It should be obvious that nothing like this can work. More generally I think you are probably confused about what macros are for: see Rainer Joswig's answer for some guidelines about that.

Consider this case

(let ((d (read-data-from-file "my-data-file")))
  (filter d ...))

d is not known until the program is run, and hence any named columns in d are not known until that time. But if filter is a macro then it is expanded before that time and hence the lambda expression it creates is created before that time and hence the names of its arguments must be known before that time. Thus the macro can't know the names of the columns in the data.

Yet again remember that macros in Common Lisp are functions whose arguments are source code and whose values are source code. Macros, therefore, are called before the program runs and can not depend on things which are known only when the program runs.

(Of course Common Lisp is also a language which provides you eval & related things, by which you can blow your legs off at the neck, but don't do that: to repurpose a famous quote due to jwz:

a Lisp programmer once had a problem. 'I know, I'll use eval' they thought. Now they have an infinite number of problems.


The answer is not to use eval but to rethink the problem. As an example: let's assume that whatever data you have has fields which live in numbered columns of some kind. And maybe there's a column-ref accessor for that.

Once you have the data (so: properly after macroexpansion time) you can compute from it the names of fields which correspond to columns. So, for instance, you could construct some kind of table which maps from field names to column numbers:

(defun compute-key-table (data)
  (let ((keytable (make-hash-table)))
    ;; keytable maps from named field to its position perhaps?
    ...
    keytable))

And now you can write key-ref accessors:

(declaim (inline key-ref (setf key-ref))
(defun key-ref (data key keytable)
  ;; This does not check the key exists
  (column-ref data (gethash key keytable))

(defun (setf key-ref) (new data key keytable)
  (setf (column-ref data (gethash key keytable)) new)

And now, using these you can write this macro:

(defmacro with-data-field-accessors (data (&rest fields) &body forms)
  ;; data is either a symbol naming the data variable, or (variable <form>)
  ;; fields are the fields we want accessors for
  (let ((<kt> (make-symbol "KT"))
        (<data> (etypecase data
                  (symbol data)
                  (cons (first data)))))
    `(let* (,@(etypecase data
                  (symbol '())
                  (cons `((,@data))))
            (,<kt> (compute-key-table ,<data>)))
       (dolist (field ',fields)
         (unless (gethash field ,<kt>)
           (error "no data field ~S" field)))
       (symbol-macrolet ,(mapcar (lambda (field)
                                   `(,field (key-ref ,<data> ',field)))
                                 fields)
         ,@forms))))

So now you can use this:

(with-data-field-accessors (x (read-data)) (foo bar)
  (+ foo bar))

Which expands into:

(let* ((x (read-data)) (#:kt (compute-key-table x)))
  (dolist (field '(foo bar))
    (unless (gethash field #:kt) (error "no data field ~S" field)))
  (symbol-macrolet ((foo (key-ref x 'foo)) (bar (key-ref x 'bar))) (+ foo bar)))

Note that this macro:

  • is given the field names, and does not try to compute them from information it cannot have;
  • expands into code which will compute the field-name-to-column-number mapping at run-time, and will check, once, whether those mappings are valid;
  • makes the field names look like variables by virtue of symbol-macrolet, which it can do since it is told the field names;
  • doesn't call eval.

Obviously this is oversimplified: presumably the data has rows or something or all this would not be worth it. Whatever the answer is, it is not to use eval.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文