在Common Lisp中,是否可以让aref在自定义类上起作用?

后端开发 2026-07-11

标准不允许从内置类继承,例如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的早期提案中曾有一些想法,但没有取得进展)。因此你不能在编译器宏中强制实现优化。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章