Scheme vs Common Lisp:哪些特征在您的项目中有所作为?[关闭]


155

在StackOverflow和此站点上都不乏含糊的“方案与通用Lisp”问题,因此,我想使这一问题更加集中。问题是针对使用两种语言进行编码的人:

在使用Scheme编码时,您最想念Common Lisp编码经验的哪些特定元素?或者,相反,在使用Common Lisp进行编码时,您从Scheme编码中错过了什么?

我不一定只是语言功能。就问题而言,以下是所有值得错过的东西:

  • 特定的库。
  • 开发环境的特定功能,例如SLIME,DrRacket等。
  • 特定实现的功能,例如Gambit将C代码块直接写入您的Scheme源的功能。
  • 当然还有语言功能。

我希望得到的答案的示例:

  • “我试图在Common Lisp中实现X,如果我拥有Scheme的一流延续性,我完全会做Y,但是我必须做Z,这更痛苦。”
  • “随着我的源代码树的增长以及我链接到越来越多的C库,在Scheme项目中编写构建过程的脚本变得越来越痛苦。在下一个项目中,我回到了Common Lisp。”
  • “我现有一个庞大的C ++代码库,对我而言,能够将C ++调用直接嵌入到我的Gambit Scheme代码中,这完全是Scheme与Common Lisp相比可能存在的任何缺点,甚至包括缺乏SWIG支持。”

因此,我希望获得战争故事,而不是诸如“方案是一种更简单的语言”之类的一般观点。


25
一个很好的,措辞充分的问题。我自己对此很好奇;希望有些人会提供两种语言的专业知识,愿意提供一些见识。
罗伯特·哈维

1
@Josh K-显然是可以回答的,但不一定有一个确定的答案。除非我敢打赌,否则会有一个,因为有人会给出非常棒的答案,每个人都像哇!
glenatron 2011年

4
@Josh:也许您不熟悉Scheme和Common Lisp。两种语言本身都有非常强大的功能,但都没有被主流接受。为什么是这样?可能是因为有许多方言。您选择哪一个?进行这样的比较可能很有启发性,并且OP谨慎地措辞了该问题,以将答案的范围限​​制为我认为非常具体且易于回答的答案。
罗伯特·哈维

13
伙计们,不要仅仅因为您不喜欢它或与它不相关而结束问题。显然,这是一个“真实”的问题;如果找不到更好的理由将其关闭,则不应投票决定关闭。
罗伯特·哈维

4
您可以给Richard Stallman发送电子邮件,以得到他的答案。
wassimans 2011年

Answers:


100

我的本科学位是认知科学和人工智能。从那以后,我对Lisp进行了一门课程的介绍。我认为这门语言很有趣(就像“优雅”一样),但是直到后来我遇到格林斯潘的《第十条规则》时,我才真正想到了很多。

任何足够复杂的C或Fortran程序都包含一个临时的,非正式指定的,bug缠身的,缓慢执行的Common Lisp的一半。

Greenspun的观点是(部分)许多复杂的程序都内置了解释器。他建议与其使用一种已经内置了解释器(或编译器)的语言(例如Lisp),而不是将解释器构建为语言。

当时,我正在开发一个相当大的应用程序,该应用程序使用用于自定义语言的自定义解释器执行用户定义的计算。我决定尝试在Lisp中重新编写其核心,作为大规模实验。

花了大约六个星期。原始代码是约100,000行的Delphi(Pascal变体)。在Lisp中,减少到10,000行。然而,更令人惊讶的是,Lisp引擎的速度提高了3-6倍。请记住,这是Lisp新手的工作!整个经历让我大开眼界。我第一次看到了将一种语言的表现力和表达力结合在一起的可能性。

一段时间后,当我开始从事基于Web的项目时,我试听了多种语言。我将Lisp和Scheme包括在内。最后,我选择了一个方案实施-Chez Scheme。我对结果非常满意。

基于Web的项目是一个高性能的“选择引擎”。从处理数据到查询数据再到页面生成,我们以多种不同方式使用Scheme。实际上,在很多地方,我们最初使用的是不同的语言,但由于下面将简要描述的原因,最终迁移到Scheme。

现在,我可以回答您的问题(至少部分)。

在试听期间,我们研究了各种Lisp和Scheme的实现。在Lisp方面,我们研究了(我相信)Allegro CL,CMUCL,SBCL和LispWorks。在方案方面,我们研究了(我相信)比格卢,鸡肉,Chez,甘比特。(语言选择是很久以前的;这就是为什么我有点朦胧。如果重要的话,我可以摘一些笔记。)

马上,我们正在寻找a)本机线程和b)Linux,Mac和Windows支持。这两个条件共同导致Allegro和Chez除外(我认为),所有人都被淘汰了-因此,为了继续进行评估,我们必须放宽对多线程的要求。

我们整理了一套小程序,并将它们用于评估和测试。那揭示了许多问题。例如:某些实现的缺陷使某些测试无法运行到完成;一些实现无法在运行时编译代码;有些实现无法轻松地将运行时编译的代码与预编译的代码集成在一起;一些实现的垃圾收集器明显比其他实现的更好(或明显更糟);等等

为了满足我们的需求,只有三个商业实现(Allegro,Chez和Lispworks)通过了我们的主要测试。在三者中,Chez顺利通过了所有测试。当时我认为Lispworks在任何平台上都没有本机线程(我认为现在已经有了),而且我认为Allegro在某些平台上只有本机线程。此外,Allegro还收取了“打电话给我们”的运行时许可费,我对此不太满意。我相信Lispworks不收取运行时费用,而Chez则具有直接(且非常合理)的安排(并且只有在您在运行时使用编译器时,它才会生效)。

在Lisp和Scheme中都产生了一些重要的代码块,下面是一些比较点和对比点:

  • Lisp环境更加成熟。您将获得更多收益。(话虽如此,更多的代码也等同于更多的错误。)

  • Lisp环境很难学习。您需要更多时间才能精通;Common Lisp是一种强大的语言,而这是在您获得将商业实现添加到其之上的库之前的。(话虽如此,Scheme的语法情况比Lisp中的任何事情都要微妙和复杂。)

  • Lisp环境在生成二进制文件时可能会更加困难。您需要“摇动”映像以删除不需要的位,并且如果在该过程中未正确执行程序,则稍后可能会遇到运行时错误。 。相比之下,使用Chez,我们可以编译一个顶层文件,其中包括它需要的所有其他文件,我们已经完成了。

我之前说过,我们最终在许多原本不打算使用的地方使用了Scheme。为什么?我可以想到三个原因。

首先,我们学会了信任Chez(及其开发者Cadence)。我们从工具中提出了很多要求,并且始终如一。例如,Chez历史上的缺陷数量很少,其内存管理器非常非常好。

其次,我们学会了热爱Chez的表演。我们正在使用一种感觉像脚本语言的东西-并且我们正在从中获得本机代码速度。对于某些无关紧要的事情,但它从未造成伤害,有时甚至起到了很大的作用。

第三,我们学会了热爱抽象方案可以提供的东西。顺便说一句,我不仅仅是指宏。我的意思是诸如闭包,lambda,尾调用之类的东西。一旦您开始使用这些术语进行思考,其他语言似乎就受到比较的限制。

方案完美吗?没有; 这是一个权衡。首先,它可以使单个开发人员更加有效-但是开发人员更难以相互理解代码,因为Scheme中缺少大多数语言(例如for循环)的路标(例如,有上百万种方法for循环)。其次,要与之交谈,从中租借,从中借贷等的开发人员池要小得多。

综上所述,我想我会说:Lisp和Scheme提供了一些其他地方无法广泛使用的功能。该功能是一种折衷,因此最好在您的特定情况下有意义。在我们的案例中,决定是否使用Lisp或Scheme的决定性因素与非常基本的功能(平台支持,平台线程,运行时编译,运行时许可)有关,而与语言或库功能无关。再一次,在我们的案例中,这也是一个折衷:通过Chez,我们获得了所需的核心功能,但是却失去了商业Lisp环境所拥有的大量库。

另外,仅重申一下:很久以前,我们研究了各种Lisps和Schemes。从那时起,它们都得到了发展和改进。


1
哇,如果它能以比Lisp实现 3-6倍的速度运行,那一定是一些非常可怕的Delphi代码!:(
梅森·惠勒

2
+1:关于此帖子的最有趣的事情是,在Lisp中完成了一个大型项目后,您从Lisp切换到Scheme。(或者也许我刚刚在comp.lang.lisp上潜伏了太久。)
Larry Coleman

25
“哇,如果它能以比Lisp实现慢3-6倍的速度运行,那一定是一些非常可怕的Delphi代码!” 是的,我认为这是我没有更好地解释它的失败。Lisp实现能够将用户表达式转换为Lisp表达式(一个简单的过程),然后将Lisp表达式编译为本地代码(经过全面优化)。这就是格林斯潘的第十条规则的含义。
Michael Lenaghan 2011年

1
很棒的答案!我会选择它,至少要等到更好的出现为止:)一个问题:您说您是根据“很久以前”的领域状态决定选择Chez Scheme的。您可以指定一年吗?
SuperElectric 2011年

11
在这一点上,LISP实现可以自由地将某些内容编译为机器代码,而不是依赖于解释器,这是微妙的且非常有用的。《 Let Over Lambda》一书指出,这就是为什么克隆PERL regexp语法的便携式Common LISP regexp软件包在性能上要优于PERL的原因。相反,PERL具有正则表达式解释器。Common LISP软件包将正则表达式编译为代码。
John R. Strohm

37

我通常不喜欢粘贴链接作为答案,但是我在此方面写了一篇博客文章。它不是详尽无遗的,但可以理解一些要点。

http://symbo1ics.com/blog/?p=729

编辑:这是要点:

  1. 存在:两个嘴唇都出现在其他一系列嘴唇之后。计划采取了最小的公理路线。CL走上了巴洛克式的路线。
  2. 案例:通常Scheme区分大小写。CL不是(尽管可以)。有时会错过这一点,但是(我)对它的实用性进行了辩论。
  3. 名称:CL中符号的名称常常是奇数和混乱的。TERPRIPROGN等等。Scheme通常具有非常明智的名称。这是CL遗漏的东西。
  4. 功能:CL具有单独的函数名称空间。这是不是错过方案。拥有单个名称空间通常可以进行非常干净的功能编程,这在CL中通常很难或尴尬。但这是有代价的-您有时必须在Scheme中混淆“ list”到“ lst”之类的名称。
  5. :我在Scheme中最想念低级的脏宏。是的,syntax-rules一切都很好,还不错,直到您想真正破解一些东西为止。另一方面,CL中有时会丢失卫生宏。没有标准的方法可以完成这项工作,这意味着需要重新发明轮子。
  6. 可移植性:这往往是CL更加轻便,尽管被标准化两种语言的情况。CL更大,因此无需外部库即可使用更多标准功能。这也意味着可以更轻松地完成更多与实现相关的事情。此外,Scheme还遭受着数以万亿计的实施,其中大多数在某种程度上是不兼容的。这使得CL非常可取。
  7. 图书馆:与我的最后一点很相关。Scheme有SRFI,但并未得到普遍认可。没有使用库的可移植方式。另一方面,CL确实有办法。Quicklisp是上帝(Xach)的礼物-一种可供使用的库。
  8. 实现:Scheme有太多的实现。没有真正的规范实现。另一方面,CL具有非常好的高性能或特定用途的实现(高性能:SBCL,商业:Allegro,嵌入式:ECL,可移植:CLISP,Java:ABCL,...)。

虽然我只是在上面第一人称对话中讲话,但应该清楚我想念的是什么,我不想念的。

[我很抱歉,如果这些都太笼统。似乎您可能需要更多具体细节。帖子中有一些细节。]


(真正的)简短预告摘要如何?^^
Dave O.

2
请内嵌亮点。答案应该自立。

1
@Dave O.和@ThorbjørnRavn Andersen:根据要求添加了摘要。谢谢。
Quadrescence 2011年

2
“巴洛克式的路线”!多么棒的表达方式。
Mark C

Common Lisp区分大小写,但在评估之前将其输入转换为大写。您可以通过引用小写字母来获得符号。名称问题是因为Scheme摆脱了不良的旧名称,而CL却没有。
David Thornley

25

我最近使用一个具有C版本和Java版本的库启动了一个家庭项目。我想在项目中使用Lisp,在使用Common Lisp,Scheme或Clojure之间花费了大约一个月的时间。我对这三个项目都有一定的经验,但只有玩具项目。在告诉您我最终选择哪一个之前,我会先向您介绍一下我在每个人中的经历。

PLT Racket有一个不错的IDE,它不仅可以让您从编辑器评估表达式,还可以让您键入方括号而不是括号,并在适当的地方将其切换回括号。Racket还具有大量安装库,甚至可以下载。可视调试器也很有帮助。

我的Common Lisp实现(SBCL)没有IDE,但是习惯于使用Emacs和SLIME的开源CL实现。这种组合可能非常有效。除了在将表达式键入源文件中时对表达式求值的能力之外,还有一个REPL,其中提供了所有可用的emacs编辑命令,因此代码复制可以高效地进行两种方式。即使显示在REPL缓冲区中的对象也可以复制和粘贴。Alt+(并且Alt+)有效地处理了匹配的括号和缩进。

上述所有Emacs功能也可用于Clojure。我对Clojure的编辑经验与Lisp相似。Java互操作性运行良好,一旦成熟,我想做一个Clojure项目。

我可以使用这三个库(Common Lisp,Racket和Clojure)访问该库,但是最终我为该项目选择了Common Lisp。决定因素是FFI在Common Lisp中更容易使用。CFFI拥有非常好的手册,其中包含示例代码和每种方法的详细说明。我能够在一个下午包装20个C函数,从那以后就不必再碰代码了。

另一个因素是,我对Common Lisp的了解比对Clojure或R6RS计划的了解要多。我已经阅读了大多数《 Practical Common Lisp》和Graham的书,并且对Hyperspec感到满意。它还不是很“轻巧”的代码,但是我相信随着我获得更多的经验,这种情况将会改变。


谢谢你的细节!我是否正确理解您认为SBCL的FFI比Clojure的FFI更易于使用?如果是这样,我将对此感到非常惊讶,因为您可以直接从Clojure调用Java方法而不必包装它们。(或者您是否还需要调用本机代码?)
SuperElectric 2011年

6
@SuperElectric:从Clojure调用“内置” Java方法很简单;调用下载的库中的Java方法:不是很多。我确实花了更多的时间来正确设置类路径和导入行,而不是花时间让我从SBCL和CFFI中使用第一个C方法。但我不是Java专家,所以您的里程可能会有所不同。
拉里·科尔曼

21

我同时在CL和Racket中编程。

我现在正在Common Lisp中开发一个网站,并为我以前的老板在Racket中编写了一套内部程序。

对于内部代码,我选择了Racket(当时称为PLT计划),因为雇主是Windows商店,而我无法让他们为LispWorks付款。Windows 唯一好的开源CL实现是(现在仍然是)CCL,它需要处理器中的SSE支持。雇主便宜,正在使用Stone Age硬件。即使雇主确实拥有不错的硬件,Common Lisp中唯一重要的GUI库还是McCLIM,该库仅在Unix上有效。Racket有一个很好的GUI库,可在Unix和Windows上运行,这对于我的项目的成功至关重要。

我花了一年多的时间来适应原始的DrRacket编辑器。EMACS无法将Racket的GUI版本(当时称为MrEd)转换为Windows上的简版。我不得不做的是无法通过一次按键就可以评估光标处的表达式。相反,我不得不手动选择S表达式,将其复制,单击REPL窗口(因为没有按键可切换到它),然后粘贴S表达式。我还必须没有一个可以向我展示我所使用的函数或宏的预期参数的编辑器。DrRacket不能替代SLIME。

雇主正在使用带有复杂XML API的专有数据库,该数据库需要加载看似不必要的信息才能响应其SELECT查询的版本。我决定同时使用HTMLPrag来向该API发出XML,并解析响应。效果很好。

我必须学习Racket过于复杂的“语法大小写”宏系统,以便编写一个宏,该宏将允许我通过键入看起来像SQL的形式来与过于复杂的XML API进行交互。如果我可以使用DEFMACRO,这部分会容易得多。但是,即使花费了更多的精力来实现,最终结果仍然是无缝的。

此外,我必须没有Common Lisp的LOOP宏。Racket仅在我编写了大部分代码后才开始提供替代方案,并且与LOOP相比,替代方案仍然很糟糕(即使Racket的开发团队坚持认为更好,它们完全是错误的)。我最后写了很多命名的LET表单,这些表单使用“ car”和“ cdr”来遍历列表。

说到car和cdr,没有什么比Scheme将(car'())解释为错误更令人沮丧的了。我利用了Racket区分大小写的优势,并实现了具有Common Lisp语义的CAR和CDR。但是,'()和#f的分隔使得返回'()作为默认值的作用要小得多。

我还结束了重新实现UNWIND-PROTECT的工作,并发明了自己的重启系统来填补Racket留下的空白。Racket社区需要了解重新启动非常有用,并且易于实现。

Racket的let-values形式过于冗长,因此我实现了MULTIPLE-VALUE-BIND。这是绝对必要的,因为Racket 要求您接收所有生成的值,无论是否使用它们。

后来,我试图在Common Lisp中编写一个eBay XML API客户端,结果发现它没有HTMLPrag之类的东西。HTMLPrag非常有用。我最终在Racket进行了那个项目。我试用了Racket的Literate Programming工具,却发现我是地球上唯一的程序员,他发现正确编写的识字代码比普通代码或不正确编写的“过多注释”识字代码难编辑。

我的新项目是在Common Lisp中完成的,这是正确的选择,因为Racket社区不相信并行性,而并行性对于该项目至关重要。我以为我可能会从球拍中错过的唯一一件事就是延续。但是,我可以通过使用重新启动来完成所需的操作,回想起来,很可能只需简单的关闭就可以完成。


2
我自己还没有尝试过,但是我看过一些将命令行球拍程序与Emacs一起使用的人的博客文章。例如:bc.tech.coop/scheme/scheme-emacs.htm
Larry Coleman

5
平心而论,这听起来像您来到Scheme时是要编写CL而不是尝试从惯用的Scheme POV中获取内容。例如,方案不是鼓励递归而不是使用循环吗?
雪橇2014年

@ArtB Scheme不仅鼓励递归,还需要递归,因此上述项目当然使用了大量递归。这样做只是增加了重复(例如,您必须在表单的每个分支中包括递归调用的副本cond)和错误(我当时是否正确编写了循环终止测试?)即使在今天,我仍然印象深刻Racket主要面向学生,而非专业程序员。每当我听到有人在使用除我之外的其他人时,他们都在使用“ Beginning Student”子语言,它是针对一堂课的。
扔掉帐户

如果要在COND之间重复代码,是说您只需要另一个功能?
雪橇

@ArtB使用不同的参数调用循环函数的函数吗?那将是毫无意义的。您几乎可以在任何人的Scheme代码中看到这种重复。Racket自己的源代码中甚至有一些示例。
扔掉帐户

5

Scheme在设计时会考虑单独的编译。结果,即使使用允许使用普通Lisp样式的defmacro而不是不良的,限制卫生的宏系统的扩展,其宏的功能通常也受到严格限制。并非总是可以定义一个宏来定义另一个宏,以便立即在下一行代码中使用。对于实现高效的eDSL编译器而言,这种可能性至关重要。

不用说,仅使用R5RS卫生宏的Scheme实现对我几乎没有用,因为我的元编程样式无法充分转换为卫生。

幸运的是,不存在该限制的Scheme实现(例如,Racket)。


1
嗨,我最近刚开始使用Scheme来让Scheme弄湿我的脚。您介意提供一个在Racket中使用非卫生宏的简单示例吗?可用的宏类型似乎是CL和Scheme之间最受争议的问题之一。
orange80 2011年

@ orange80,一种方法是使用docs.racket-lang.org/mzlib/mzlib_defmacro.html并且,当然,在R6RS模式下,限制较少。
SK-logic

@ SK-logic您如何处理如此不起眼的宏?
2014年

1
@ArtB,我正在将eDSL实现为编译器功能,这可能会对它们的源AST起到很大作用。在这种方法中,卫生是一个令人讨厌的事情。您可以看一下它是如何工作的:github.com/combinatorylogic/mbase
SK-logic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.