本页面以前的内容很大程度上与Guile和Emacs集成的历史有关,请参阅GuileEmacsHistory。
基于Guile的Emacs是GNU Emacs的一个分支,它用Guile的Elisp编译器替换了Emacs自己的EmacsLisp引擎。它并不试图删除Elisp,而是通过完全向后兼容来成为未来规范的GNU Emacs。最好将其命名为“GuileEmacs”或“Guile/Emacs”,以表明它仍然是GNU Emacs。
一个优点是Elisp可能会执行得更快,因为Guile使用了一个带有许多优化遍的编译器塔,并最终编译成Guile VM字节码,这比当前的Elisp字节码更高效。此外,从3.0版开始,Guile就有了原生JIT。将来,Guile可能会实现某种形式的AOT编译。
第二个优点是,为Elisp实现Guile编译器塔和VM能够实现的一些附加语言特性会更容易,比如一个完整的数值塔,它不仅包含无限大的整数(GNU Emacs 27.0.50已经拥有),而且还包含精确的有理数、虚数等)、记录类型(如改进的Defstruct)、类Clos的OOP、FFI、可组合的延续、模块系统、卫生宏、多值返回和线程(GNU Emstruct。
第三个优势是所有的GuileAPI/库都可以用于Elisp代码,无论它们是用什么语言实现的,因为GuileVM上的不同语言可以很好地互操作,特别是如果它们都是Lisp的话。C实现的函数(ELISP术语中的“subr”)、ElISP函数、Scheme过程等都编译成相同的“过程”数据类型,该数据类型可能出现在ElISP符号的函数槽中,绑定到Scheme变量,否则在两种环境中都是一级对象,可以显式地或通过语言的正常语法调用函数来实现或应用。类似地,其他数据类型在语言之间是统一的;ELISP整数和精确方案整数、不精确的方案数字和ELISP浮点数、ELISP圆锥单元格和方案对、符号等在所有语言中都是相同的数据类型。(不过,字符串是个例外;请参见下面的内容。)。因此,人们通常可以使用用另一种语言编写的库,就好像它是用同一种语言编写的一样。
最后,可以用Scheme代替Elisp编写Emacs扩展,在Elisp中可以将Elisp函数和变量名称空间作为模块加载(比如为每个模块添加前缀,这样就不会覆盖像‘car’这样的常见名称)。
最新的公开可用的GuileEmacs状态可以在BT Templeton的Emacs存储库和Guile存储库中找到。与此同时,后者进入了guile主要git资源库的wip-elisp分支。两者的“wip”分支都表示GuileEmacs的最新状态,这两个分支都是测试它所必需的。(首先构建Guile repo的wp分支,然后使用它构建Emacs repo的wp分支。)。
在GoogleSummerOfCode2014年末,Emacs的Elisp引擎完全被Guile所取代,而且大多数东西都可以在™上运行。一些性能退化仍然存在,特别是对于使用动态作用域的程序。请参见GuileEmacsTODO。
当没有为Elisp文件启用词法作用域时,Elisp定义变量以及let绑定变量都是动态限定作用域的。它们与Scheme中的参数对象使用相同的机制。目前,这些都没有达到应有的效率,而且缓冲区局部变量的存在和Elisp绑定机制的其他奇怪之处给Guile实现ElISP带来了额外的麻烦,到目前为止,这会导致非常大的性能回归。这个问题有望很快得到解决。
;;此缓冲区用于您不想保存的笔记和Lisp评估。;;如果要创建文件,请使用C-xC-f,;;访问该文件,然后在该文件自己的缓冲区中输入文本。(Load-library";Scheme";)(Scheme-interaction-mode);;这就是Elisp;现在我们正在使用Scheme:(use-module((elisp-symbols)#:prefix ev-));变量((elisp函数)#:前缀ef-)((elisp-plists)#:前缀EP-))(ef-message(ef-Symbol-Name';Cool!))";Cool!";
注意:您需要使用C-j进行计算,而不是使用C-x、C-e或C-M-x。
对于纯ELISP用户来说,似乎没有长期的问题,但是那些想要将ELISP与Scheme混合在一起的用户将面临以下挑战:
Elisp字符串以扩展的UTF-8格式编码,而Guile仅使用libunstring。出于这个原因,对于初学者来说,Elisp字符串将是与其他Guile字符串(比如来自Scheme代码)不同的数据类型。这不会给纯Elisp代码带来问题,但会限制人们从Elisp无缝使用Scheme库,以及在Scheme中编写Emacs扩展的能力,因为需要在Elisp和Scheme字符串之间显式地来回转换。
Scheme对false boolean列表和空列表(也称为null)使用单独的对象;Elisp对两者都使用nil。这对语言的互操作提出了挑战。Guile通过在内部支持所有三个对象(false、null和nil)来解决这个问题,但是让Elisp和Scheme以程序员通常不会注意到的方式处理它们。
在Elisp中,这三个对象(几乎)完全无法区分,即使使用‘eq’也是如此,这意味着一个人可以忽略整个问题,并且所有的Elisp代码继续按原样工作。FALSE和NULL对象可能表现不同的一种方式是在‘打印’它们时;这还没有确定它们应该具有什么行为:它们可能打印除“NIL”之外的其他东西,因此如果它们像方案→、ElISP→和Print→一样往返读取→和ElISP→方案,则方案代码不会丢失任何信息;或者可以接受信息的丢失,使它们在从ELISP代码打印时打印“nil”。
在Scheme中,nil(其语法为#nil)可以同时充当false和null的角色(在‘if’、‘not’、null?等中),但是这三个角色都不相等?(更不用说eqv了?或等式?)。为了彼此。这是因为如果x和y相等,y和z相等,那么x和z也必须相等,但是在Scheme中我们不能使false和null相等。通过一些代码示例来详细说明:(if#nil 0 1)产生1,(and(not#nil)(null?#nil))产生true,(cons';foo#nil)产生(Foo)。这意味着只要用户使用‘if’、‘not’、null?等而不是将相等与文字对象进行比较,一切都很好。
一个值得注意的算法是找到两个共享结构的列表的公共尾巴,该算法不修改就不能在Scheme中工作。该算法将两个列表切割成相同的长度,然后并行遍历它们,并做一个等式?检查每对节点。通常情况下,这至少会在到达两个列表末尾的()时结束(这意味着它们唯一的公共尾部是空列表),但是在处理混合Elisp数据的Scheme代码中,必须分别检查(和(null?list1-ail)(空?list2-ail)的情况(实际上测试两个中的一个就足够了,因为它们具有相同的长度),否则必须提前规范化列表的终止值。
更奇怪的是,在Elisp中,nil和true对象也是符号,因为它们毕竟是“nil”和“t”。尽管它们是相同的对象,但在Scheme中不是符号。Elisp‘SYMBOL’函数和方案符号?这里的过程与其他处理符号的过程完全不同。
长话短说:Elisp通常不受影响,但是如果您的代码在预期的“nil”处打印出一些“false”或“null”对象,请不要惊慌。如果您编写与Elisp数据交互的Scheme代码,请确保使用‘if’/‘not’/null?/等,而不是将相等与#f或';()进行比较。也不要假设列表以相等的对象结尾,这在以前是有保证的,因为它们都以NULL结尾。
虽然过程在语言之间是共享的,但宏更为困难。当然,Elisp宏只是一个过程,因此原则上它们可以作为不卫生的Scheme宏工作,而ElISP代码通过与Scheme相同的中间语言,所以也许卫生的Scheme宏可以在ElISP上工作。
前者可能无法正常工作,因为宏输出中出现的Elisp函数和变量名将插入到未定义的Scheme代码中。
后者可能会奏效。方案的卫生宏不输出方案代码(原始的SXPRs);它们以中间语言(IL)输出代码,该中间语言可以直接引用任何模块中的绑定,并且表示较低级别的Guile支持的所有代码概念。在进一步编译或解释之前,Scheme和Elisp都已转换为此IL,因此在ElISP代码上使用Scheme中定义的卫生宏不会有任何问题;它只需将宏模板的Scheme正文和输入到宏的Elisp代码片段分别编译(为IL),并合并引用正确的Scheme和Elisp变量和函数绑定的这些IL片段即可。甚至是与‘let’绑定的宏的标识符输入,如(语法-规则()((with-foo foo body.))。(Foo What))Body.),在概念上应该可以工作(在词法上将‘foo’绑定到‘body’中的Elisp代码)。
还应该可以为Elisp实现一个安全的宏定义表单,尽管‘def宏’本身可能不能被设置为安全的,因此使用Scheme中当前所有现有的Elisp宏仍然是有问题的。它们可能需要慢慢重新实现为Elisp或Scheme中定义的卫生宏。
Guile基本上通过POSIX线程支持并发性。在Guile-Emacs中,可以启动任意数量的线程执行Scheme,但这并不能解决Emacs充满线程不安全的数据结构和全局资源的问题,因此使用并发Scheme代码编写Emacs扩展可能会被证明充满了意想不到的障碍和漏洞。一些Elisp函数甚至滥用Emacs的单线程特性,使全局资源发生变异,然后再将它们变异回来,从而产生引用透明的假象,因此在并发代码中漠不关心地使用ElispAPI仍然是有问题的。