Common lisp 在公共Lisp中定义多个后端的惯用方法?

Common lisp 在公共Lisp中定义多个后端的惯用方法?,common-lisp,clos,Common Lisp,Clos,我想用多个用户界面后端(例如文本和图形)编写代码,这样它们就很容易切换。我的方法是使用CLOS: (defgeneric draw-user-interface (argument ui) (:documentation "Present the user interface") (:method (argument (ui (eql :tui))) (format t "Textual user interface! (~A)" argument)) (:method (a

我想用多个用户界面后端(例如文本和图形)编写代码,这样它们就很容易切换。我的方法是使用CLOS:

(defgeneric draw-user-interface (argument ui)
  (:documentation "Present the user interface")
  (:method (argument (ui (eql :tui)))
    (format t "Textual user interface! (~A)" argument))
  (:method (argument (ui (eql :gui)))
    (format t "Graphical user interface! (~A)" argument)))
这种方法乍一看似乎还可以,但也有一些缺点。为了简化调用,我定义了将在每个函数调用中使用的参数ui类型,以简化后端切换,但在使用高阶函数时会导致问题:

(defparameter *ui-type* :tui
  "Preferred user interface type")

(draw-user-interface 3 *ui-type*)

;;; I can't use the following due to the `ui' argument:
;(mapcar #'draw-user-interface '(1 2 3))

;;; Instead I have to write this
(mapcar #'(lambda (arg)
            (draw-user-interface arg *ui-type*))
        '(1 2 3))

;; or this
(mapcar #'draw-user-interface
        '(1 2 3)
        (make-list 3 :initial-element *ui-type*))

;; The another approach would be defining a function
(defun draw-user-interface* (argument)
  (draw-user-interface argument *ui-type*))

;; and calling mapcar
(mapcar #'draw-user-interface* '(1 2 3))
如果采用这种方法,我们可以将通用函数命名为%draw user interface,而包装器函数仅命名为draw user interface

这是一种有效的方法还是有更直接的方法?问题在于为相同的功能提供不同的后端,而不一定是用户界面

另一种情况可能是,当我有许多相同算法的实现(针对速度、内存消耗等进行了优化)时,我希望以干净的方式切换它们,保留接口和参数类型。

所谓“后端”是指前端,对吗?与中一样,用户与之交互的部分,而不是处理应用程序逻辑的部分

最干净的选择是将您的程序划分为一个库(它提供程序的所有逻辑和功能,没有任何UI代码)和两个完全独立的UI程序,它们本身不实现任何功能,而只是使用库。当然,如果需要,您可以使用一个包装器来选择要运行的接口。您应该将每个组件保留在各自的系统中

编辑:当您想要在不同的算法之间切换时,最好的选择可能是简单地将接口定义为类,将所有不同的算法定义为子类

(defclass backend () ())
(defgeneric do-something (backend x y))

(defclass fast-backend (backend) ())
(defmethod do-something ((backend fast-backend) x y)
  (format t "Using fast backend with arguments ~a, ~a.~%" x y))

(defclass low-mem-backend (backend) ())
(defmethod do-something ((backend low-mem-backend) x y)
  (format t "Using memory efficient backend with arguments ~a, ~a.~%" x y))

(defun main (x y)
  (let ((backends (list (make-instance 'fast-backend)
                        (make-instance 'low-mem-backend))))
    (dolist (b backends)
      (do-something b x y))))
另一个编辑:如果您需要能够使用像
mapcar
这样的函数,您可能需要一个包含当前后端的全局变量。然后定义一个使用全局函数的包装器函数

(defparameter *backend* (make-instance 'fast-backend))
(defun foobar (x y)
  (do-something *backend* x y))

(defun main (x y)
  (foobar x y)
  (let ((*backend* (make-instance 'low-mem-backend)))
    (foobar x y))
  (foobar x y))

我将把后端实现为单独的类,而不是传递一个关键字,因为这将允许我将不同的状态挂接到一个对象中并保持不变


否则,我可能会使用您提到的通用函数设计。

通用Lisp接口管理器和多个后端

CLOS中支持多个后端的UI层的一个例子是CLIM,即公共Lisp接口管理器您可以研究它的软件设计。请参阅下面的链接。例如,请参见端口(与显示服务的连接)、介质(绘图发生的位置、与某种图纸的输出状态相对应的协议类)、图纸(用于绘制和输入的表面,大致类似于分层窗口)、嫁接(代表主窗口的图纸)等类周围的协议。。。在一个应用程序中,打开一个端口(例如到一个特定的窗口系统,如X11/Motif),应用程序的其余部分应该基本不变地运行。CLIM的体系结构将其所有服务映射到一个特定的CLIM后端,该后端提供到X11/Motif(或您将使用的任何端口)的接口

例如,
draw line
功能将绘制图纸、流和介质。通用函数
medium draw line*
将为一个或多个medium子类实现不同版本的绘制线

一般来说,这不是很成功,因为便携式用户界面层带来了复杂性,需要大量的工作来开发和维护。在90年代中期,Lisp应用程序的市场很小(参见AI Winter),CLIM不够好,实现是封闭源代码或专有的。后来开发了一个名为McCLIM的开源/免费实现,它创建了工作软件,但最终开发人员/用户失去了兴趣

一段历史

在以前,Symbolics开发了一种称为“动态窗口”的用户界面系统。它于1986年发行。它在Symbolics操作系统中运行,可以利用其本机OS/硬件组合和X11。大约从1988年开始,开发了基于CLOS的便携式版本。第一个可用版本(特别是1991年的1.0版)可在多个平台上使用:generas、X11、Mac和Windows。后来开发了一个新版本(版本2.0),它再次在各种系统上运行,但包括一个复杂的面向对象层,该层提供了一个更明确的后端层,称为Silica。这个后端层不仅支持便携式绘图,而且还支持抽象窗口系统的一部分。更为雄心勃勃的部分,如支持外观和感觉(滑块、窗口样式、滚动条、菜单、对话框元素等)的自适应,尚未完全解决,但至少作为第一代版本提供

指针

(PDF)

(PDF)


规范(包括二氧化硅):

为了补充其他答案,本用例有两个库。这两个都是从中得到灵感的,你应该去看看

一种是允许您定义对象的不同“视图”。它不使用CLOS,而是基于原型的CL对象系统。早期的方法是基于CLOS的。它将向标准插槽对象添加3个附加插槽。属性标签、属性函数和属性值。属性函数a中的函数将插槽值转换为最终表示形式,如果函数为nil,则按原样使用属性值中的值。标签是对值的描述,类似于html5形式的标签。

我所说的“后端”是指“后端”-我们可能有三种相同算法的实现(我们称之为XXX123),每种算法都有自己的特点(一种是针对速度优化的,另一种是针对内存消耗的,第三种是两者之间的折衷)。我想切换实现