【问题标题】:In common-lisp is there a way to code an 'apply' equivalent for macros?在 common-lisp 中,有没有办法为宏编写一个“应用”等效项?
【发布时间】:2017-11-25 09:54:18
【问题描述】:

我有一个宏:mac1(&rest args),它接受任意数量的参数

由于我不能将apply 与宏一起使用,并且由于我无法控制宏的实现,我如何才能使给定列表的函数fun1 (lst) 使用扩展列表调用宏?

更新:虽然我的问题已经足够了,但似乎最好陈述我的真实情况:

Postmodern 提供查询宏:

macro: query (query &rest args/format) 

比如你这样称呼它:

(query "select * from example where col1 = $1::integer and col2 = $2::date" 123 "2017-01-01" :str-alists)

好的,现在想象一下,在运行时我通过了这个列表:

'((:query "select * from table1 where c1 = $1 and c2 = $2 and c3 = $3"
   :params (1 "2017-01-01" 3) :return :str-alists

  (:query "select * from tab2 where c3 = $1"
   :params ("somevalue") :return :lists)
   .
   .
   .
  ))

我必须进行查询。我需要定义一个将这样的列表作为参数并进行查询的函数。但是由于查询是一个宏,我有以下问题:我需要在某个地方评估列表,以便将它们传递给宏。目前我正在这样做(想法来自@melpomene cmets):

(defun macro-apply(q p ret)
  (eval (macroexpand `(query ,q ,@p ,ret))))

所以函数变成:

(defun exec-queries (lst)
    (mapc 
       (lambda(x) (macro-apply 
                      (getf x :query) 
                      (getf x :params) 
                      (getf x :return))
       lst)) 

有没有更好的方法,或者我错过了什么明显的东西?

【问题讨论】:

  • (macroexpand `(mac1 ,@lst))?
  • 因为它是来自外部库的宏,并且该库不提供函数。通常,您在函数中调用宏并直接将其参数传递给它。但在我的情况下,在运行时,我反复得到一些参数列表(我事先不知道并且长度不同),我被要求用这些参数调用宏。
  • @melpomene 我已经更新了我的问题以更好地说明。
  • 顺便说一句,您不需要调用 MACROEXPAND。无论如何,EVAL 都会这样做。
  • 一般是这样的。请注意,EVAL 可能不会编译并使用解释器来运行它。还需要解析重复的查询/...每次使用它们时。

标签: macros common-lisp


【解决方案1】:

关于您的实际问题,请注意宏使用低级包cl-postgres 来使用函数实现查询(请参阅http://quickdocs.org/postmodern/api#system-cl-postgres)。宏只是底层 API 的语法糖。让我们看看如何使用参数化查询扩展宏:

CL-USER> (macroexpand
           '(postmodern:query 
               "select * from table where col1=$1:integer" 
               20))

(PROGN
 (CL-POSTGRES:PREPARE-QUERY POSTMODERN:*DATABASE* ""
                            "select * from table where col1=$1:integer")
 (CL-POSTGRES:EXEC-PREPARED POSTMODERN:*DATABASE* "" (LIST 20)
                            'CL-POSTGRES:LIST-ROW-READER))

与 SQL 一样,您首先定义了 prepared 语句,以避免注入攻击。如果您经常重复使用相同的查询,那么只准备一次会更有效(请参阅defprepared)。然后,您执行查询。

当您还指定结果类型(查询中的最后一个关键字参数)时,宏会调用未导出的 reader-for-format 函数,以了解对每一行使用哪个回调函数。

(macroexpand
 '(postmodern:query "select * from table where col1=$1:integer"
   20
   :str-alist))
(MULTIPLE-VALUE-CALL
    #'(LAMBDA
          (&OPTIONAL (POSTMODERN::ROWS) (POSTMODERN::AFFECTED) &REST #:G843)
        (DECLARE (IGNORE #:G843))
        (IF POSTMODERN::AFFECTED
            (VALUES (CAR POSTMODERN::ROWS) POSTMODERN::AFFECTED)
            (CAR POSTMODERN::ROWS)))
  (PROGN
   (CL-POSTGRES:PREPARE-QUERY POSTMODERN:*DATABASE* ""
                              "select * from table where col1=$1:integer")
   (CL-POSTGRES:EXEC-PREPARED POSTMODERN:*DATABASE* "" (LIST 20)
                              'CL-POSTGRES:ALIST-ROW-READER)))
T

由于宏已经为您完成了所有工作,您可以有效地将查询包装在新表单中并评估它们,前提是您不评估任意 Lisp 表单(准备好的语句可以防止 SQL 注入,但 EVAL 开辟了另一个攻击向量;检查您的输入)。

或者,尝试了解如何使用查询的构建块来生成您自己的查询解释器。乍一看,获取任意查询并对其进行处理似乎是某种“内部平台效应”,但也许您有一个很好的用例。

【讨论】:

  • 听起来像,但事实并非如此。我仍然会写查询。我只是不想专门为人们可能想到的每个报告创建一个 api 端点和前端输入字段。
  • @Paralife 这是为编写 SQL 查询的专家用户准备的吗? (只是出于好奇)
  • 不适合我。我将编写查询。我收到很多要求在 Web 应用程序中提供自定义查询的请求,我不得不在前端处理大量样板文件来发布查询的组件或页面(输入字段、api 端点等)。现在,我将为通用报告创建一个前端模板页面/组件,并将查询和参数类型写入 lisp 文件中作为 plist。我会将参数发送给客户端,它会呈现输入字段,返回参数值,然后我会将它们与查询文本一起输入到我的函数中。
  • 我知道我可以编写最少的代码来直接调用每个报告的查询宏,但这是另一个麻烦,而且报告有时是短暂的。现在,我将拥有一个目录,其中包含文件,每个文件都包含 plist 中的报告定义。如果我删除该文件,则该报告将被删除。如果我添加一个文件,用户前端会“神奇地”提供一份报告,而无需执行任何其他操作。
  • @Paralife 感谢您的解释
【解决方案2】:

也可以使用 COMPILE 甚至 COMPILE-FILE + LOAD 来处理。

例如,在某些情况下,以某种格式输出查询表单并嵌入到文件中可能会很有用。然后,您可以编译该文件并加载代码 - 在加载时执行或稍后执行。

原始版本:

  1. 生成一个文件,将IN-PACKAGE 表单放入其中并将一个或多个查询表单转储到其中。
  2. COMPILE-FILE编译文件
  3. 错误处理...
  4. 加载文件然后执行查询

如果你想对结果做点什么,你必须在文件中添加更多的代码......

此示例要求函数 compile-file 和必要的库/宏代码在运行时可用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-18
    • 2011-03-08
    • 2016-02-21
    • 1970-01-01
    • 2020-08-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多