在公共Lisp中模拟Clojure样式的可调用对象
在Clojure中,哈希映射和向量实现在公共Lisp中模拟Clojure样式的可调用对象,clojure,common-lisp,lisp-2,callable-object,Clojure,Common Lisp,Lisp 2,Callable Object,在Clojure中,哈希映射和向量实现调用,因此它们可以用作函数 (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中创建这些对
调用
,因此它们可以用作函数
(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中的函数对象,似乎拥有一个单独的函数名称空间会使CL的问题复杂化,而在Scheme中,您可以通过闭包来实现这一点。公共Lisp的前身中的可调用对象示例 以前提供过可调用对象。例如,在Lisp机器Lisp中: 公共Lisp中的绑定 Common Lisp有单独的函数和值名称空间。因此
(array 10 1 20)
只有在array
是表示函数名称空间中函数的符号时才有意义。因此,函数值将是一个可调用的数组
将绑定到变量的值作为函数使用,这在很大程度上违背了函数和值使用不同名称空间的目的
(let ((v #(1 2 3)))
(v 10)) ; doesn't work in Common Lisp
在函数和值具有不同名称空间的语言中,上述内容毫无意义
(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)
CommonLisp不允许以任何琐碎或相对简单的方式实现这一点
旁注:
我们可以在将函数和值与CLO集成的方向上实现一些东西,因为CLO通用函数也是类STANDARD-generic-FUNCTION
的CLOS实例,并且可以拥有和使用该类的用户定义子类。但这通常不会被利用
建议
因此,最好调整到不同的语言风格,并按原样使用CL。在这种情况下,CommonLisp不够灵活,无法轻松地合并这样的功能。对于较小的代码优化,不省略符号是一般的CL风格。危险在于混淆和只写代码,因为很多信息都不是直接出现在源代码中。尽管可能没有一种方法可以准确地完成您想要做的事情,但也有一些方法可以将类似的事情拼凑在一起。一个选项是定义一个新的绑定表单,使用callable,它允许我们将函数本地绑定到可调用对象。例如,我们可以
(with-callable ((x (make-array ...)))
(x ...))
大致相当于
(let ((x (make-array ...)))
(aref x ...))
以下是可调用的的的可能定义:
(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))
好多了。我们现在有了一个表单,带有callable,它将定义允许我们访问对象的本地函数,还有一个宏defcallable,它允许我们定义如何创建其他类型的可调用版本。这种策略的一个缺陷是,每当我们想要使对象可调用时,都必须显式地使用with callable
另一个类似于可调用对象的选项是Arc的结构访问。基本上x.5访问x中索引5处的元素。我能够在CommonLisp中实现这一点。你可以看到我为它写的代码,还有。我也对它进行了测试,这样你就可以看到使用它是什么样子了
我的实现是如何工作的,我编写了一个宏w/ssyntax,它查看主体中的所有符号,并为其中一些定义宏和符号宏。例如,x.5的符号宏是(get x 5),其中get是我定义的访问结构的通用函数。这方面的缺陷是,我总是必须在任何我想使用ssyntax的地方使用w/ssyntax。幸运的是,我能够将它隐藏在一个类似于defun的宏def中。我同意Rainer Joswig的建议:最好熟悉Common Lisp的做事方式——就像普通Lisp程序员在切换到Clojure时熟悉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
,并setf
将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的打印程序做些有趣的事情。也可以允许设置数组的值,但这可能需要单独的符号。实际上,在Clojure中,这超出了名称空间的分隔。以下内容在Clojure中可以正常工作:
({:a“first key”:b“second key”}:a)
将产生“first key”
,因此Clojure在这方面的行为类似于Lisp Machine Lisp。因此,是的,在Clojure中,对于某些类型,“数据在函数名称空间中”。“我们想要吗?”显然,korrok对此的回答是肯定的,但Co