4Manuals

  • PDF Cloud HOME

Lisp-参数列表中间的可选参数 Download

    Lisp-将未引用的列表传递给宏 根据输入数据集使用SAS生成SQL代码 查找输入中出现前哨值(例如-999)之前的非负数的平均值

我目前有一个定义如下的宏:

(defmacro some-macro (generic-name (&rest args) &body body) 
  ...)

我现在想向此宏添加一个额外的参数,这是用户可以选择提供的一种标志,它将改变宏的行为。下面的代码应该是可能的:

(some-macro some-name (arg1 arg2) (print (+ arg1 arg2)))
(some-macro :flag some-name (arg1 arg2) (print (special-+ arg1 arg2)))

我特意做了一个概念性的例子,重点放在重要的内容上,而不是我要实现的实际宏上。真正的宏是不同的,而且更复杂。如您所见,现在可以使用:flag参数(只要前缀为':',flag可以是任何单词)并且没有flag参数都可以调用该宏。有没有一种方法可以不用将&optional关键字放在参数列表的末尾(即,它确实需要在该第一个位置)。

4 个答案:

答案 0 :(得分:3)

&optional只能在位置参数的末尾(可以在&rest或&body之后)。即使您可以更早提出来,如果还有&rest或&body,它怎么知道您是否提供了可选参数?例如。如果lambda列表是

(&optional arg1 arg2 &rest rest-args)

和电话是

(func-name 1 2 3 4 5)

它可以是arg1 = 1, arg2 = 2, rest-args = (3 4 5)或arg1 = NIL, arg2 = 1, rest-args = (2 3 4 5)。

您需要定义宏以采用单个&rest参数。然后,您可以检查第一个参数是否为关键字,更新参数列表以添加默认值,然后使用destructuring-bind进行解析。

(defmacro some-macro (&rest all-args) 
  (unless (keywordp (first all-args))
    (push nil all-args)) ;; flag defaults to NIL
  (destructuring-bind (flag generic-name (&rest args) &body body) all-args
    ...))

答案 1 :(得分:2)

(some-macro :flag some-name (arg1 arg2) (print (special-+ arg1 arg2)))

(some-macro some-name (arg1 arg2) (print (+ arg1 arg2)))

人们只能猜测出什么有用,因为宏的设计在某种程度上取决于更多的上下文:实际用于什么。

例如,我们在CLOS中编写

(defmethod foo :around ((a class-a)) ...)

首先出现名称,然后是零个或多个方法限定符(这里是:around,然后是arglist。在典型的定义宏(以{{1开头的那些宏中)先在名称前加上一个标志会很奇怪。 }}。

为此,我们需要自己编写arglist的宏结构,因为它与内置宏arglist模式不匹配。

def

在其他宏中,我们可能会写

(defmacro some-macro (name &rest args)
  (let* ((qualifiers (loop for arg in args
                           until (listp arg)
                           collect (pop args)))
         (arg-list (pop args))
         (body args))

    ... ; return something for the example

    ))

例如类似于

(some-macro some-name (arg1 arg2 :flag)
  (print (special-+ arg1 arg2)))

(defmacro some-macro (some-name (arg1 arg2 &optional flag) &body body)
  ...)

尽管上面使用的是关键字,而不是可选的。

或者我们可能要写:

(with-input-from-string (stream string :start 10)
  (... ))

如果只有三个标志,则还可以生成三个具有不同名称的不同宏并将其删除。

答案 2 :(得分:0)

废话少说:将带有标志的宏语法重写为另一个带有包含标志的固定参数的宏:

(defmacro some-macro-w-flags (flags name (&rest args) &body body)
   ...)

(defmacro some-macro (&rest args)
  (let ((flags))
    (loop while (keywordp (car args))
          do (push (pop args) flags))
    `(some-macro-w-flags ,flags ,@args)))

一些测试:

[1]> (macroexpand-1 '(some-macro abc (1 2 3)))
(SOME-MACRO-W-FLAGS NIL ABC (1 2 3)) ;
T
[2]> (macroexpand-1 '(some-macro :foo abc (1 2 3)))
(SOME-MACRO-W-FLAGS (:FOO) ABC (1 2 3)) ;
T
[3]> (macroexpand-1 '(some-macro :foo :bar abc (1 2 3)))
(SOME-MACRO-W-FLAGS (:BAR :FOO) ABC (1 2 3)) ;
T

答案 3 :(得分:0)

在设计诸如宏之类的东西时要考虑的一件事(记住,当您在设计宏时,您正在设计一种编程语言)是人们期望阅读该语言的方式。如果您正在设计的编程语言是CL的适度超集,或者与CL非常接近,那么您可能不希望违反CL程序员在读取CL代码时的期望,或更普遍的是Lisp程序员在读取Lisp代码时的期望。

[请注意,以下大部分内容是观点:人们显然有不同的观点-这些是我的,他们比任何人都没有权利。]

那么,这些期望是什么?好吧,它们可能包括以下两个:

  • 人们从左到右阅读CL,因此表单左端的内容在视觉上更重要;
  • CL中许多现有的表单看起来像(<operator> <thing> ...) –到目前为止,表单中的前两个子表单最为有趣,而第二个子表单通常比第一个更有趣。考虑一下(defun foo ...),(dolist (x ...) ...),(let ((x y) ...) ...)。

一个违反这些期望的例子是一个对象系统,它使用某些send操作显式地与消息传递配合工作(我认为Old Flavors做到了,而New Flavors却没有,但是我的记忆现在很模糊)。使用这些代码编写的代码看起来像(send <object> <message> ...):许多形式的第一个单词是send。这意味着这个视觉上重要的代码读取位置已被完全浪费,因为它始终是同一个单词,而重要的位置现在是第二和第三子窗体。嗯,相反,我们可以忽略整个send并写(message object ...)或(object message ...)。 CLOS本质上采用了这些选项中的第一个,其中“消息”是一个泛型函数,当然,泛型函数可以专注于多个参数,这会破坏整个消息传递范式。但是您可以像编写CLOS一样编写CLOS,并且它可以工作,这意味着它与其他很多CL代码的外观相同。

好吧,让我们看一下您的宏的两种情况:

(some-macro some-name ...)

这很好。

(some-macro :flag some-name ...)

但这在视觉上处于第二位置,这与宏所涉及的内容无关:它只是一些可选参数。有趣的是,现在是第三名。

那么,我们该如何解决呢?事实证明,CL中已经存在一个很好的示例:defstruct。 defstruct有两种基本情况:

(defstruct structure-name
  ...)

和

(defstruct (structure-name ...)
  ...)

这两个都满足最重要的前两个位置的要求,同时允许使用可选参数并在视觉上清楚地指示使用它们的时间。

(除了:defclasss通过将选项放在末尾来做不同的事情,如:

(defclass name (...supers...)
  (...slot specifications...)
  ...options...))

我想都可以。)

因此,重做宏的一种方法就像defstruct。在这种情况下,您将拥有

(some-macro some-name (...)
  ...)

或

(some-macro (some-name :flag) (...)
  ...)

您可以轻松实现该目标:

(defmacro some-macro (thing (&rest args) &body forms)
  (multiple-value-bind (the-thing the-options)
      (etypecase thing
        (symbol (values thing '()))
        (cons
         (destructuring-bind (proposed-name . proposed-options) thing
           (unless (symbolp proposed-name)
             (error ...))
           (unless (proper-list-p proposed-options)
             (error ...))
           (values proposed-name proposed-options))))
    ...))

事实上,我会做得比这更进一步:人们期望关键字参数具有值,因为在大多数其他地方,它们都具有值。所以有

(some-macro (some-name :flag t) (...)
  ...)

符合那个期望。这样做还有一个好处,就是您可以使用CL的参数解析来获取信息:

> (destructuring-bind (&key (flag nil flagp)) '(:flag t)
    (values flag flagp))
t
t

例如。如果您这样编写宏,则可能会得到如下所示的结果:

(defmacro some-macro (thing (&rest args) &body forms)
  (multiple-value-bind (the-thing flag flagp)
      (etypecase thing
        (symbol (values thing nil nil))
        (cons
         (destructuring-bind (proposed-name (&key (flag nil flagp))) thing
           (unless (symbolp proposed-name)
             (error ...))
           (values proposed-name flag flagp))))
    ...))



Similar searches
    在macOS Mojave上读取ARP缓存 Flutter:倒数计时器,显示分钟和秒 使用多种流协议(例如mms://,rstp://等)响应本地实时流 Samsung RSG5D Refrigerator User Manual 重命名字段以解析GraphQL中的数据