包与命名空间与模块

时间:2016-12-25 13:09:28

标签: clojure module scheme lisp common-lisp

根据http://www.phyast.pitt.edu/~micheles/scheme/scheme29.html

“值得一提的是,如果您在实践中使用包系统(如Common Lisp)或命名空间系统(如Clojure),变量捕获变得非常罕见。在Scheme中,使用模块系统,卫生是必不可少...“

这里使用的术语包系统,命名空间系统和模块系统有什么区别?

请注意,这是关于Lisp-1与Lisp-2(链接文档单独讨论)。我猜它可能与方式有关,在Common Lisp中,尝试在两个不同的包中使用相同的符号可以得到两个不同的同名符号。

2 个答案:

答案 0 :(得分:4)

我认为包系统和模块系统之间的一个共同区别是包系统处理将源文本映射到名称,而模块系统处理映射名称到含义。 (我不知道命名空间系统是什么,但我怀疑它是一个包系统,可能没有一流的包,甚至是一流的名字?)

在Lisp的上下文中,名称是符号,因此当您在模块中读取具有符号语法的内容时,包系统会控制您获得的符号(特别是它所包含的包)系统总是得到相同的符号,但其值取决于模块状态。

以下是使用CL包系统和Racket模块系统来解决这些问题的一个例子。请注意,我是一个CL人员:我理解CL包系统比我理解的Racket模块系统或Racket / Scheme宏要好得多。

我想说我想定义某种符号代数系统,我希望能够在(+ a b)a时使用b之类的语法可能是cl:+不明白的事情,例如多项式或其他东西。

好吧,我可以在CL中使用这样的包定义来做到这一点:

(defpackage :org.tfeb.symalg
  (:use)
  (:export "+" "-" "*" "/"))

(let ((p (find-package :org.tfeb.symalg)))
  (do-external-symbols (s (find-package :cl))
    (ecase (nth-value 1 (find-symbol (symbol-name s) p))
      ((nil)
       (import s p)
       (export s p))
      ((:external)
       nil)
      ((:inherited :internal)
       (error "package botch")))))

(defpackage :org.tfeb.symalg-user
  (:use :org.tfeb.symalg))

(请注意,在现实生活中,你显然会写一个宏,以声明和更灵活的方式做上述发型:事实上,互联网上的某些人写了一个名为'管道的系统&# 39;要做到这一点,有一天他可能会再次发布它。)

这样做是为了创建一个包org.tfeb.symalg,类似于cl ,除了,某些特定符号不同,以及包org.tfeb.symalg-user使用这个包而不是cl。在该套餐中,(+ 1 2)表示(org.tfeb.symalg:+ 1 2),但(car '(1 . 2))表示(cl:car '(1 . 2))

但是

(defmethod foo (a)
  (:method-combination +))

表示

(defmethod foo (a)
  (:method-combination org.tfeb.symalg:+))

现在我遇到了一些麻烦:我想在任何地方使用符号+ 作为符号我必须输入cl:+。 (这个具体的例子很容易解决:我只需要为org.tfeb.symalg:+定义一个方法组合,我可能想在任何情况下都这样做,但还有其他情况。)

这使得这种语言的重新定义变得很痛苦。在我想使用作为语言的一部分的名称(符号)作为符号的情况下。

比较球拍:这是Racket中的一个小模块定义,它提供(或实际上没有)某些算术符号的变体版本):

#lang racket

(provide
 (rename-out
  (plus +)
  (minus -)
  (times *)
  (divide /)))

(define (plus . args)
  (apply + args))

...

(define plus-symbol '+)

这样做是说,如果你使用这个模块,那么符号+就是模块中符号plus的值,等等。但符号是相同的符号。如果你正在使用模块,你可以很容易地检查这个,例如(eq? '+ plus-symbol),它将返回#t:当你输入+时,你得到的符号没什么好笑的,它是从这些符号到它们的值的映射。

所以这更好:如果Racket有一个CLOS风格的对象系统(它可能有六个,其中一些可能有一半工作),那么+方法组合就可以工作了,一般来说任何关心符号的东西都会以你想要的方式运作。

除了你在CL中最常见的一个操作符号作为符号的东西是宏。如果你尝试将CL方法应用于Racket中的宏,那么到处都是头发。

在CL中,方法通常是这样的:

  1. 以某种方式定义包,并且这些定义在整个系统的编译,加载和评估过程中都是相同的。
  2. 定义宏。
  3. 编译程序时会扩展宏。
  4. 程序已加载并运行。
  5. 这一切都运行良好:如果我的宏是在+表示org.tfeb.symalg:+的情况下定义的,那么,如果其扩展涉及+,则它确实涉及org.tfeb.symalg:+,并且只要该包存在于编译时(它将自编译时间和宏扩展时间交织在一起)并且在运行时并且定义就在那里,那么一切都会很好。

    但是在Racket中它不是一回事:如果我写一个宏,那么我知道符号+只是符号+。但+ 意味着在不同时间可能完全不同,具体取决于模块状态。例如,这可能意味着在编译时+意味着Racket的原生添加功能,因此编译器可以将其优化到地面,但也许在运行时它意味着其他东西,因为模块状态现在不同了。符号+不包含足够的信息以了解其含义。

    如果您还考虑了Scheme人员非常关心关于以干净的方式排序此类事物的信息,并且绝对不满意与CL处理宏的问题,不要再那么努力了。只使用gensyms,它一切都会很好,没有一个是由Schemes作为Lisp-1s帮助的,你可以看到这里有一些相当重要的问题需要解决。

答案 1 :(得分:3)

Common Lisp有一个符号包系统

<强>符号

  • @latest;包cl-user::create
  • 中的符号CREATE
  • CL-USER;导出包cl-user:create
  • 中的符号CREATE
  • CL-USER;符号create在 - 读取时 - 当前包
  • CREATE;包:create
  • 中的符号CREATE
  • KEYWORD;符号#:create,不在任何包

包CL-USER中的示例

让我们告诉读者CREATE是当前的包裹:

CL-USER

功能(in-package "CL-USER")

CL-USER::CREATE

使用上述功能

创建代码的宏
(defun create (x)        ;  CL-USER::CREATE
  (list :list x))

如果我们留在这个包中,我们可以定义一个本地函数, 这将影响上述全球功能。

(defmacro m (x)
  `(create ,x))    ; here we use CL-USER::CREATE

所以(defun foo () (flet ((create (x) ; CL-USER::CREATE (vector :vector x))) (m (create 1)))) ; CL-USER::CREATE (m (create 1))。 两者都使用本地函数。

包BAR中的示例

现在我们可以将(cl-user::create (cl-user::create 1))函数移到另一个包FOO,我们将在此处定义:

BAR

让我们告诉读者使用此包(defpackage "BAR" (:use "COMMON-LISP") (:export "FOO") (:import-from "CL-USER" "M")) 作为当前包:

BAR

现在,如果我们定义与上面类似的函数(in-package "BAR")

FOO

所以(defun foo () (flet ((create (x) ; BAR::CREATE (vector :vector x))) (m (create 1)))) ; BAR::CREATE (m (create 1))。 这意味着用户代码不会遮蔽宏使用的函数。

但我们可以明确使用(cl-user::create (bar::create 1))符号:

CL-USER::CREATE

所以(defun foo () (flet ((cl-user::create (x) ; CL-USER::CREATE (vector :vector x))) (m (cl-user::create 1)))) ; CL-USER::CREATE (m (create 1))

使用未分隔的符号

Common Lisp还提供未在包中实现的符号。 一个特殊的读取器宏表示法可以引用这样的符号倍数 表达式中的时间:(cl-user::create (cl-user::create 1))是标记,#1=引用标记的对象。

#1#

所以(defun foo () (flet ((#1=#:create (x) ; #:CREATE (vector :vector x))) ; (m (#1# 1)))) ; #:CREATE from above (m (#1# 1)), 其中`#:create symbol是引用的未分隔符号 从上面。

使用计算符号

Common Lisp还允许使用(cl-user::create (#:create 1)) 阅读器宏在读取时运行代码。

#.

可能会创建与此类似的代码:

(defun foo ()
  #.(let ((create-symbol (gensym "CREATE-")))
      `(flet ((,create-symbol (x)              ; #:CREATE-NNN
                (vector :vector x))) 
         (m (,create-symbol 1)))))             ; #:CREATE-NNN from above

所以(DEFUN FOO () (FLET ((#:CREATE-815 (X) ; #:CREATE-815 (VECTOR :VECTOR X))) (M (#:CREATE-815 1)))) ; #:CREATE-815 from above (m (#:CREATE-815 1)), 其中(cl-user::create (#:create-815 1))符号是引用的符号 从上面。