在 Common Lisp 中模拟 Clojure 风格的可调用对象

emulating Clojure-style callable objects in Common Lisp

在 Clojure 中,哈希映射和向量实现 invoke,因此它们可以用作函数,例如

(let [dict {:species "Ursus horribilis"
            :ornery :true
            :diet "You"}]
  (dict :diet))

lein> "You"

或者,对于向量,

(let [v [42 613 28]]
  (v 1))

lein> 613

通过让 Clojure 实现 IFn,可以在 Clojure 中创建可调用对象。我是 Common Lisp 的新手 -- 可调用对象是可能的吗?如果是的话,实现它涉及什么?我真的很想能够做这样的事情

(let ((A (make-array (list n n) ...)))
   (loop for i from 0 to n
         for j from 0 to m
      do (setf (A i j) (something i j)))
   A)

而不是让代码乱七八糟 aref。同样,如果您可以访问其他数据结构的条目,那会很酷,例如字典,同理。

我查看了 Lisp/Scheme 中关于函数对象的 wiki entry ,似乎拥有一个单独的函数命名空间会使 CL 的事情复杂化,而在 Scheme 中你可以使用关闭。

Common Lisp 前身中的可调用对象示例

之前已经提供了可调用对象。例如在 Lisp Machine Lisp:

Command: ("abc" 1)            ; doesn't work in Common Lisp
#\b

Common Lisp 中的绑定

Common Lisp 有独立的函数和值名称空间。所以 (array 10 1 20) 只有在 array 是表示函数命名空间中的函数的符号时才有意义。因此函数值将是一个可调用数组。

将值绑定到变量作为函数主要违背了函数和值的不同命名空间的目的。

(let ((v #(1 2 3)))          
  (v 10))                    ; doesn't work in Common Lisp

对于函数和值具有不同命名空间的语言,以上内容毫无意义。

FLET 用于函数而不是 LET

(flet ((v #(1 2 3 4 5 6 7))) ; doesn't work in Common Lisp
  (v 4))                     

这意味着我们会将数据放入函数命名空间。我们想要那个吗?不是真的。

在函数调用中作为函数的文字数据。

还可以考虑至少允许文字数据在直接函数调用中充当函数:

(#(1 2 3 4 5 6 7) 4)         ; doesn't work in Common Lisp

而不是

(aref #(1 2 3 4 5 6 7) 4)

Common Lisp 不允许以任何微不足道或相对简单的方式进行。

旁注:

可以在将函数和值与 CLOS 集成的方向上实现一些东西,因为 CLOS 通用函数也是 class STANDARD-GENERIC-FUNCTION 的 CLOS 实例,并且可以拥有和使用用户定义的子classes。但这通常不会被利用。

推荐

所以,最好适应不同的语言风格,照原样使用CL。在这种情况下,Common Lisp 不够灵活,无法轻松合并这样的功能。不为次要代码优化省略符号是一般的 CL 风格。危险的是混淆和只写代码,因为很多资料都没有直接在源代码里,然后。

虽然可能没有办法完全按照您的意愿进行操作,但有一些方法可以将类似的事情组合在一起。一种选择是定义一个新的绑定形式,with-callable,它允许我们在本地将函数绑定到可调用对象。例如我们可以使

(with-callable ((x (make-array ...)))
  (x ...))

大致相当于

(let ((x (make-array ...)))
  (aref x ...))

以下是 with-callable 的可能定义:

(defmacro with-callable (bindings &body body)
  "For each binding that contains a name and an expression, bind the
   name to a local function which will be a callable form of the
   value of the expression."
  (let ((gensyms (loop for b in bindings collect (gensym))))
    `(let ,(loop for (var val) in bindings
                 for g in gensyms
                 collect `(,g (make-callable ,val)))
       (flet ,(loop for (var val) in bindings
                    for g in gensyms
                    collect `(,var (&rest args) (apply ,g args)))
         ,@body))))

剩下的就是为 make-callable 定义不同的方法,return 闭包用于访问对象。例如,这是一个为数组定义它的方法:

(defmethod make-callable ((obj array))
  "Make an array callable."
  (lambda (&rest indices)
    (apply #'aref obj indices)))

由于这种语法有点难看,我们可以使用宏使其更漂亮。

(defmacro defcallable (type args &body body)
  "Define how a callable form of TYPE should get access into it."
  `(defmethod make-callable ((,(car args) ,type))
     ,(format nil "Make a ~A callable." type)
     (lambda ,(cdr args) ,@body)))

现在要使数组可调用,我们将使用:

(defcallable array (obj &rest indicies)
  (apply #'aref obj indicies))

好多了。我们现在有一个形式,with-callable,它将定义允许我们访问对象的局部函数,以及一个宏,defcallable,它允许我们定义如何制作其他类型的可调用版本。这种策略的一个缺陷是,每次我们想让一个对象可调用时,我们都必须显式地使用 with-callable。


另一个类似于可调用对象的选项是 Arc 的结构访问 ssyntax. Basically x.5 accesses the element at index five in x. I was able to implement this in Common Lisp. You can see the code I wrote for it here, and here. I also have tests for it so you can see what using it looks like here

我的实现是如何工作的,我写了一个宏 w/ssyntax,它查看正文中的所有符号并为其中一些定义宏和符号宏。例如,x.5 的符号宏是 (get x 5),其中 get 是我定义的访问结构的通用函数。这样做的缺陷是我总是必须在任何我想使用 ssyntax 的地方使用 w/ssyntax。幸运的是,我能够将它隐藏在一个类似于 defun 的宏 def 中。

我同意 Rainer Joswig 的建议:熟悉 Common Lisp 的做事方式会更好——就像 Common Lisp 程序员在切换到Clojure。然而, 有可能做你想做的一部分,正如 malisper 复杂的答案所显示的那样。这是一个更简单的策略的开始:

(defun make-array-fn (a) 
  "Return a function that, when passed an integer i, will 
  return the element of array a at index i."
  (lambda (i) (aref a i)))

(setf (symbol-function 'foo) (make-array-fn #(4 5 6)))

(foo 0) ; => 4
(foo 1) ; => 5
(foo 2) ; => 6

symbol-function访问符号foo的函数单元,setfmake-array-fn创建的函数对象放入其中。由于此函数位于函数单元格中,因此可以在列表的函数位置使用 foo。如果你愿意,你可以将整个操作包装成一个宏,例如像这样:

(defmacro def-array-fn (sym a)
  "Define sym as a function that is the result of (make-array-fn a)."
  `(setf (symbol-function ',sym)
         (make-array-fn ,a)))

(def-array-fn bar #(10 20 30 40))

(bar 0) ; => 10
(bar 1) ; => 20
(bar 3) ; => 40

当然,以这种方式定义的 "array" 不再 看起来 像一个数组。我想你可以用 CL 的打印例程做一些花哨的事情。也可以允许设置数组的值,但这可能需要单独的符号。