在Common Lisp中,是否可以让aref在自定义类上起作用?
标准不允许从内置类继承,例如simple-string或 vector。如果我们想要自定义向量或字符串,我们必须把向量或字符串包装在我们自己的类或结构体中。至少我是这么理解的,请纠正我如果我错了。把包装当然带来的问题是,内置运算符现在必须作用于槽位,而不是对象。换句话说,我们必须把向量槽取出,传给arenf运算符。一个可选的做法是覆盖arenf运算符,并对类型进行分支,像下面这样:
(defstruct mystring data)
(defun aref (array &rest subscripts)
(if (typep array 'mystring)
(apply #'cl:aref (mystring-data array) subscripts)
(apply #'cl:aref array subscripts)))
这当然在每次元素访问时都要付出类型检查和分支的成本。如果对自定义类重载operator[],C++就不需要为此付出该成本。之所以如此,是因为C++的类型与类型系统的工作方式决定了编译时就能解决。如何在Common Lisp中实现类似的效果?
据这个标准的增补所述,如果定义一个编译器宏,它们是“全局的”。我以为可以为arenf定义一个编译器宏,在编译时完成上述操作:
(sb-ext:without-package-locks
(define-compiler-macro aref (array &rest subscripts)
(typecase array
(mystring
`(cl:aref ,(mystring-data array) ,@subscripts))
(otherwise
`(cl:aref ,array ,@subscripts)))))
这似乎并不奏效,无论我选择哪种优化级别,以及我如何与彼此匹配类型等。我还尝试从 &environment参数获取类型数据,如这个回答中所述,但也没有任何区别。
我也为SBCL写了arenf的 deftransform。它基本上是对他们自己arenf转换的一个直接拷贝,只是为我的包装器做了适配,但似乎也没有起效:
(macrolet ((with-row-major-index ((node array indices index &optional new-value)
&rest body)
........
(deftransform aref ((array &rest indices) (mystring &rest t) * :node node)
(with-row-major-index (node (mystring-data array) indices index)
(hairy-data-vector-ref (mystring-data array) index)))
(deftransform (setf aref)
((new-value array &rest subscripts) (t mystring &rest t) * :node node)
(with-row-major-index (node (mystring-data array) subscripts index new-value)
(hairy-data-vector-set (mystring-data array) index new-value))))
这些代码只是用于示意的片段,尽管定义并编译起来很顺利,但该转换似乎也没有起效。
如果可能,优先在可移植的CL中实现这一点,应该如何做?
解决方案
像普通宏一样,你需要生成测试 array 的运行时值的代码。你的代码是在测试编译时的值,该值将是符号 arr。
(sb-ext:without-package-locks
(define-compiler-macro aref (array &rest subscripts)
`(typecase ,array
(mystring
(cl:aref (mystring-data ,array) ,@subscripts))
(otherwise
(cl:aref ,array ,@subscripts)))))
如果环境中有类型声明,编译器应该能够利用它们来优化 typecase。
在宏中没有可移植的方法来访问编译时的类型提示。参数 &environment 是一种不透明类型,没有标准的访问器(在X3J13的早期提案中曾有一些想法,但没有取得进展)。因此你不能在编译器宏中强制实现优化。