2

在 Clojure 中,hash-map 和向量实现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。我是 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 条目,似乎拥有一个单独的函数命名空间会使 CL 的问题变得复杂,而在 Scheme 中,你可以用闭包来做到这一点。

4

3 回答 3

5

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

之前已经提供了可调用对象。例如在Lisp 机器 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 通用函数也是该类的 CLOS 实例,STANDARD-GENERIC-FUNCTION并且可以拥有和使用该类的用户定义子类。但这通常不会被利用。

推荐

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

于 2015-01-17T08:03:17.937 回答
4

尽管可能没有办法完全按照您的意愿去做,但有一些方法可以将类似的东西组合在一起。一种选择是定义一个新的绑定形式,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 定义不同的方法,这些方法返回用于访问对象的闭包。例如,这是一个为数组定义它的方法:

(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。基本上 x.5 访问 x 中索引 5 处的元素。我能够在 Common Lisp 中实现这一点。您可以在此处此处查看我为它编写的代码。我也对它进行了测试,所以你可以在这里看到使用它的样子。

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

于 2015-01-17T16:03:14.363 回答
0

我同意 Rainer Joswig 的建议:最好适应 Common Lisp 的做事方式——就像在切换到 Clojure 时,让 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访问符号 的函数单元foosetf并将创建的函数对象make-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

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

于 2015-01-18T07:26:55.667 回答