【问题标题】:Macro with a list of macros as argument in Common Lisp在 Common Lisp 中以宏列表作为参数的宏
【发布时间】:2018-06-08 06:07:39
【问题描述】:

在 Common Lisp 中,如何定义一个“元宏”,它将宏列表(和其他参数)作为参数,并组合这些宏以生成所需的代码。

这个问题相当于写了一个“高阶宏”,它从其他宏的变量列表中定义了一个宏。

提示问题的具体情况对我来说是一个 CLSQL 的实验,我想从 CLSQL-testsuite 中重新表达员工类

(clsql:def-view-class employee ()
  ((employee-id
    :db-kind :key
    :db-constraints (:not-null)
    :type integer)
   (first-name
    :accessor employee-first-name
    :type (string 30)
    :initarg :first-name)
   (last-name
    :accessor employee-last-name
    :type (string 30)
    :initarg :last-name)
   (email
    :accessor employee-email
    :type (string 100)
    :initarg :email)
   (company-id
     :type integer
     :initarg :company-id)
   (company
    :accessor employee-company
    :db-kind :join
    :db-info (:join-class company
              :home-key companyid
              :foreign-key companyid
              :set nil))
   (manager-id
    :type integer
    :nulls-ok t
    :initarg :manager-id)
   (manager
    :accessor employee-manager
    :db-kind :join
    :db-info (:join-class employee
              :home-key managerid
              :foreign-key emplid
              :set nil))))

作为

(def-view-class-with-traits employee ()
  (trait-mapsto-company trait-mapsto-manager)
  ((employee-id
    :db-kind :key
    :db-constraints (:not-null)
    :type integer)
   (first-name
    :accessor employee-first-name
    :type (string 30)
    :initarg :first-name)
   (last-name
    :accessor employee-last-name
    :type (string 30)
    :initarg :last-name)
   (email
    :accessor employee-email
    :type (string 100)
    :initarg :email)))

在定义复杂的数据库模式时,使用这种技术将有利于保持一致性和简洁性。

我将我需要的两个特征定义为

(defmacro trait-mapsto-company (class super slots &rest cl-options)
  (declare (ignore super slots cl-options))
  (let ((company-accessor-name
          (intern (concatenate 'string (symbol-name class) "-COMPANY"))))
    `((company-id
       :type integer
       :initarg :company-id)
      (company
       :accessor ,company-accessor-name
       :db-kind :join
       :db-info (:join-class company
                 :home-key companyid
                 :foreign-key companyid
                 :set nil)))))

(defmacro trait-mapsto-manager (class super slots &rest cl-options)
  (declare (ignore super slots cl-options))
  (let ((manager-accessor-name
          (intern (concatenate 'string (symbol-name class) "-MANAGER"))))
    `((manager-id
       :type integer
       :initarg :manager-id)
      (manager
       :accessor ,manager-accessor-name
       :db-kind :join
       :db-info (:join-class manager
                 :home-key managerid
                 :foreign-key emplid
                 :set nil)))))

但是我写def-view-class-with-traits 的尝试被挫败了。

(defmacro def-view-class-with-traits (class super traits slots &rest cl-options)
  (let ((actual-slots
          (reduce (lambda (trait ax) (append (apply trait class super slots cl-options) ax))
                  traits
                  :initial-value slots)))
    `(clsql:def-view-class ,class ,super ,actual-slots ,@cl-options)))

在用于归约的 lambda 中,trait 代表宏,而我对 apply 的使用对 Lisp 没有任何意义——这是正确的! – 但希望将我的意图传达给其他程序员。

如何让def-view-class-with-traits以适当的方式处理traits的宏列表?

【问题讨论】:

  • 为什么不能使用继承?
  • @DanRobertson 在这种情况下,不能将继承与数据库规范化一起使用。

标签: common-lisp clsql lisp-macros


【解决方案1】:

如果您将特征定义为类本身并使用普通继承,我会发现它不会那么令人惊讶:

(def-view-class trait-mapsto-company ()
  ((company-id
    :type integer
    :initarg :company-id)
   (company
    :accessor company
    :db-kind :join
    :db-info (:join-class company
              :home-key company-id
              :foreign-key company-id
              :set nil))))

(def-view-class trait-mapsto-manager ()
  ((manager-id
    :type integer
    :initarg :manager-id)
   (manager
    :accessor manager
    :db-kind :join
    :db-info (:join-class manager
              :home-key managerid
              :foreign-key emplid
              :set nil)))

(def-view-class employee (trait-mapsto-company trait-mapsto-manager)
  ((employee-id
    :db-kind :key
    :db-constraints (:not-null)
    :type integer)
   (first-name
    :accessor employee-first-name
    :type (string 30)
    :initarg :first-name)
   (last-name
    :accessor employee-last-name
    :type (string 30)
    :initarg :last-name)
   (email
    :accessor employee-email
    :type (string 100)
    :initarg :email)))

这当然不会使访问器名称依赖于继承类的名称,但你真的想要吗?我的观点是,这种写法表明这实际上会破坏解耦原则。

【讨论】:

  • 我认为这是解决 OP 实际问题的正确方法,前提是 clsql 对继承做正确的事情。
  • 我理解你的意思,但是这强调了继承,这对我来说似乎有点奇怪,因为我考虑的是状态而不是行为。
  • 继承主要不是关于行为。关于对象/类的继承意味着扩展。从这方面来看,多态行为仅源于对扩展透明的需求。在 Common Lisp/CLOS 中,类继承和多态行为的解耦表现为类和泛型函数的分离。
  • 通常不会定义foo-trait 类的任何行为。除了行为,例如您可以要求从 manager-trait 继承的任何东西的经理。您在问题中提出的是一种临时的、非正式指定的、低质量的继承版本。为什么不直接使用好的版本。
【解决方案2】:

“调用”宏的方法是使用macroexpand-1

(defmacro def-view-class-with-traits (class super traits slots
                                      &rest cl-options
                                      &environment env)
  (let ((tslots
           (loop for m in traits
                 append (macroexpand-1 (list* m class super slots options)
                                       env))))
    `(def-view-class ,class ,super (,@tslots ,@slots) ,@cl-options)))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-06
    • 1970-01-01
    • 2019-11-03
    • 2016-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多