`intern`函数的目的是什么?

问题描述 投票:0回答:2

我正在关注一篇文章,其中作者定义了以下宏:

(defmacro make-is-integral-multiple-of (n)
  (let ((function-name (intern (concatenate
                                'string
                                (symbol-name :is-integral-multiple-of- )
                                (write-to-string n)))))
    `(defun ,function-name (x)
       (equal 0 (mod x, n)))))

这个宏很容易阅读和理解,但我想知道:什么时候以及为什么我们明确需要

intern
函数?

删除它会破坏宏,然后返回错误:

The value "IS-INTEGRAL-MULTIPLE-OF-3"
is not of type
  (OR SYMBOL CONS).

这是否意味着每次宏定义新符号时都必须调用

intern
?除了
intern
语句外,
defmacro
是否有任何用途?欢迎大家提出见解。

macros common-lisp symbols
2个回答
7
投票

为什么要实习符号?

符号通常被认为是命名对象,用于在 Lisp 中命名函数、变量、类型和其他事物。当符号具有相同的名称(并且位于同一个包中)时,它们通常应该是同一个对象。

(eq 'foo 'foo)

如果执行上述代码,那么它通常会计算为

T
:从输入中读取相同的符号时,它也会成为相同的对象。

保持从名称(->字符串)到符号的映射的数据结构在 Common Lisp 中称为

package
。现在有两个基本操作:

  • intern
    :在具有特定名称的包中查找符号。如果没有,它会创建一个新符号。

  • find-symbol
    :在包中查找某个名称的符号。它返回找到的符号。如果没有符号,则返回两个值:NIL 和 NIL。第二个值表示未找到具有此类名称的符号。

reader(Lisp 中读取 s 表达式并创建数据结构的功能)还需要能够查找符号并在必要时创建新符号。

例如

(read-from-string "FOO")
会在内部调用
(intern "FOO" *package*)

这允许我们在 REPL 中输入:

CL-USER > (eq 'foo 'foo)
T

阅读第一个

foo
,让读者创建一个新符号(如果没有)。然后读取第二个
foo
只会返回名为
foo
的现有符号。

eq
然后将执行类似于指针比较的操作,以查看两个对象(此处 -> 符号)是否是相同的对象。在包中内部查找名称通常是哈希表查找。

历史上,在 LISP 中,有时使用列表或数组来跟踪所有符号。然后使用 btree 数据结构来加快查找速度。 Common Lisp 实现使用内部哈希表将名称(-> 字符串)映射到符号。

函数名称

函数名称必须是

(OR SYMBOL CONS)
类型。这是 Common Lisp 标准所要求的。

因此名称需要是符号列表。通常 Lisp 中的函数名必须是符号。对于 setf 函数 来说,它们可以是列表是相对特殊的。在 Common Lisp 中,它只能是像

(setf foo)
这样的列表,其中
setf
作为第一个符号。在普通 Common Lisp 中不允许使用其他列表作为函数名称。 (旁注,较旧的 Lisp Machine Lisp 有其他列表作为函数名称)。

(defun foo (bar)     ; FOO is a symbol and the name of the function
  (+ 42 bar))

以下内容很不寻常,但实际上是 Common Lisp 的一个特性:

(defun (setf a) (new-value thing)    ; (setf a) is the name of the function
  (setf (first thing) new-value))

使用 INTERN 和 MAKE-SYMBOL 生成函数名称

所以,如果你想生成一个新的函数名,它需要是一个符号。首先将新名称生成为字符串,然后从该字符串生成符号。

创建符号的方法有多种。

INTERN
将查看该符号是否已存在于包中(默认为当前包)。如果它不存在,它将创建一个新符号并将该符号保留在该包中。

也可以使用

MAKE-SYMBOL
创建符号,但该符号不会位于任何包中。这使得通常很难访问该符号。

通常,函数名称应该是一个符号,它被驻留在某个包中。仅在极少数情况下,例如计算代码的某些情况下,使用未驻留符号作为函数名称才会有用。


7
投票

intern
查找或创建 符号与包中提供的名称。

这意味着当宏创建符号时必须使用它。 您示例中的宏是一个相当典型的用例,只不过人们通常使用像

#:is-integral-multiple-of-
这样的未驻留符号而不是关键字
:is-integral-multiple-of-

在其他情况下它也可能有用。 一般来说,包是一个将字符串映射到符号的特殊用途表,

intern
对应于
(setf gethash)

例如,您可以使用以下方法之一来保存数据:

(defvar *operators* (make-hash-table :test 'equal))
(defstruct operator name help function)
(defun make-op (name help function)
  (setf (gethash name *operators*)
        (make-operator :name name
                       :help help
                       :function function)))
(make-op "build-house"
         "construct a house out of the supplied materials"
         (lambda (bricks mortar) ...))
(funcall (operator-function (gethash "build-house" *operators*))
         (list "brick1" "brick2" ...)
         (make-mortar))

(defpackage #:operators)
(defun make-op (name help function)
  (let ((op (intern name #:operators)))
    (setf (symbol-value op) help
          (fdefinition op) function)))
(make-op "build-house"
         "construct a house out of the supplied materials"
         (lambda (bricks mortar) ...))
(funcall #'operators::build-house
         (list "brick1" "brick2" ...)
         (make-mortar))

使用

symbol-name
访问操作员名称,帮助是
symbol-value

© www.soinside.com 2019 - 2024. All rights reserved.