包与命名空间与模块

rwa*_*ace 6 lisp scheme module clojure common-lisp

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

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

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

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

tfb*_*tfb 5

我认为包系统和模块系统之间的共同区别是,包系统负责将源文本映射到名称,而模块系统则负责将名称映射到含义。(我不知道什么是名称空间系统,但是我怀疑它是一个软件包系统,也许没有一流的软件包,甚至没有一流的名称?)

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

这是使用CL封装系统和Racket的模块系统如何区别这些示例的示例。请注意,我是一名CL人士:我对CL软件包系统的了解比对Racket的模块系统或Racket / Scheme宏的了解要好得多。

假设我想定义某种符号代数系统,并且希望能够使用诸如(+ a b)when abmay这样的语法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))
Run Code Online (Sandbox Code Playgroud)

(请注意,在现实生活中,您显然会编写一个宏来以声明性且更灵活的方式完成上述操作:确实,互联网上某个人编写了一个名为“管道”的系统来做到这一点,有一天可能会再次发布。)

这里做的事情是创建一个包,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 +))
Run Code Online (Sandbox Code Playgroud)

意味着

(defmethod foo (a)
  (:method-combination org.tfeb.symalg:+))
Run Code Online (Sandbox Code Playgroud)

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

在我想使用作为语言一部分的名称(符号)的情况下,很难做这种“重新定义语言的位”的事情。

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

#lang racket

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

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

...

(define plus-symbol '+)
Run Code Online (Sandbox Code Playgroud)

这样做是说,如果你使用这个模块,那么价值的符号+是符号价值plus的模块等等英寸 但是符号是相同的符号。而且,例如(eq? '+ plus-symbol),如果使用模块,您可以轻松地检查它,它将返回#t:键入时得到的符号+对所有元素都没有影响,这完全是这些符号到它们的值的映射。

因此,这要好得多:如果Racket有一个CLOS风格的对象系统(它可能有大约六个,其中一些可能工作一半),那么+方法组合就可以了,并且通常任何关心符号的东西都可以工作。您想要的方式。

除了在CL中将符号作为符号处理符号的最常见的操作之一就是宏。而且,如果您尝试将CL方法应用于Racket中的宏,则到处都是毛发。

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

  1. 这些软件包以某种方式定义,并且这些定义在系统的编译,加载和评估中都是相同的。
  2. 定义宏。
  3. 程序将与正在扩展的宏一起编译。
  4. 该程序已加载并运行。

而这一切工作正常:如果我的宏在一个情况下定义的+方式org.tfeb.symalg:+的话,如果它的扩张涉及到+它真正涉及到org.tfeb.symalg:+,而只要在这个包在编译时存在(它会因为编译时和宏展开时相互交织)在运行时定义在那里,那么一切都会很好。

但这在Racket中是不一样的:如果我写了一个宏,那么我知道符号+只是symbol +。但是根据模块状态的不同,+ 含义可能会完全不同。例如,这可能意味着在编译时+意味着Racket的本机加法函数,因此编译器可以将其优化为底层,但在运行时可能意味着其他事情,因为模块状态现在不同。该符号+没有足够的信息来知道其含义。

如果还考虑到信息计划的人真正关心这个类的事情整理出一个干净的方式,并且是绝对不会幸福与CL的方法来“停止思考宏,以便硬只是使用与gensyms,它都将很好”,Lisp-1作为Schemes并没有帮助,您可以看到这里有一些相当重要的问题需要解决。