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

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

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


特定库。等。

特定实现的功能,例如Gambit将C代码块直接写入Scheme源码的功能。

当然还有语言功能。


我希望得到的答案的例子:继续,我完全会做Y,但是我必须做Z,这更痛苦。”

“随着我的源代码树的增长以及我链接到越来越多的C库,在Scheme项目中编写构建过程的脚本变得越来越痛苦。在下一个项目中,我回到了Common Lisp。 />
”“我有一个庞大的现有C ++代码库,对我而言,能够将C ++调用直接嵌入我的Gambit Scheme代码中,完全可以使Scheme与Common Lisp相比有任何缺点,甚至包括缺少SWIG支持”。


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

评论

一个很好的,措辞充分的问题。我自己对此很好奇;希望有一些人同时具备两种语言的专业知识,愿意提供一些见识。

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

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

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

您可以给理查德·斯托曼发邮件给他。

#1 楼

我的本科学位是认知科学和人工智能。从那时起,我对Lisp进行了一门课程的介绍。我认为该语言很有趣(例如“优雅”),但直到后来我遇到Greenspun的《第十条规则》时,我并没有真正考虑它:


任何足够复杂的C或
Fortran程序包含一个临时的,
非正式指定的,臭虫缠身的,缓慢的
Common Lisp一半的实现。
部分)许多复杂的程序都有内置的解释器。他建议不要使用像Lisp这样的语言,它已经内置了解释器(或编译器),而不是将解释器构建为一种语言。一个相当大的应用程序,它使用针对自定义语言的自定义解释器来执行用户定义的计算。我决定尝试在Lisp中重新编写其核心,作为一个大型实验。

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

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

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

在试听期间,我们研究了各种Lisp和Scheme实现。在Lisp方面,我们研究了(我相信)Allegro CL,CMUCL,SBCL和LispWorks。在计划方面,我们研究了(我相信)比格鲁(Bigloo),鸡肉,切兹(Chez),甘比(Gambit)。 (语言选择是很久以前的;这就是为什么我有点朦胧。如果重要的话,我可以摘一些笔记。) 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历来的缺陷数量很少,而其内存管理器则非常非常好。我们正在使用一种感觉像脚本语言的东西-并且我们正在从中获得本机代码速度。对于一些无关紧要的东西,但是它从来没有造成伤害,有时却起到了很大的作用。顺便说一句,我不仅仅是指宏。我的意思是诸如闭包,lambda,尾调用等之类的东西。一旦您开始用这些术语进行思考,其他语言似乎就比较受限制。方案完美吗?没有;这是一个权衡。首先,它可以使单个开发人员更加有效-但是开发人员更难以彼此混淆代码,因为Scheme中缺少大多数语言(例如for循环)的路标(例如,有上百万种方法for循环)。其次,要与之交谈,从中租借,从中借用的开发人员的数量要少得多。

总而言之,我想我要说的是:Lisp和Scheme提供了一些尚未广泛使用的功能其他任何地方。该功能是一种折衷,因此最好在您的特定情况下有意义。在我们的案例中,决定使用Lisp还是Scheme的决定因素与语言或库功能相比,与基本功能(平台支持,平台线程,运行时编译,运行时许可)的关系更大。同样,在我们的例子中,这也是一个折衷方案:使用Chez,我们获得了所需的核心功能,但是却失去了商业Lisp环境所拥有的大量库。很久以前在各种Lisps和Schemes中;从那时起,它们都得到了发展和改进。

评论


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

–梅森·惠勒
2011-3-29在0:21

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

–拉里·科尔曼(Larry Coleman)
2011-3-29在12:35

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

– Michael Lenaghan
2011-3-29在18:03



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

–超级电动
2011年3月31日21:47

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

– John R. Strohm
2011年6月2日,12:59



#2 楼

我通常不喜欢粘贴链接作为答案,但是我就此事写了一篇博客文章。它不是详尽无遗的,但可以通过它获得一些要点。

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

编辑:这是原理要点:



存在:两个大裂缝都排在其他大裂缝之后。计划采取了最低限度的公理路线。 CL选择了巴洛克式的路线。

案例:通常Scheme区分大小写。 CL不是(尽管可以)。有时会遗漏它,但是(我自己)在讨论它的实用性。

名称:CL中符号的名称常常是奇数和混乱的。 TERPRIPROGN等。Scheme通常具有非常明智的名称。在CL中缺少此功能。

功能:CL具有单独的函数名称空间。计划中不容错过。具有单个名称空间通常允许进行非常干净的功能编程,这在CL中通常很难或不方便。但这是有代价的-有时您必须在Scheme中混淆诸如“ list”到“ lst”之类的名称。是的,syntax-rules很好,还不错,直到您想真正破解一些东西为止。另一方面,CL中有时会丢失卫生宏。

可移植性:尽管这两种语言都已标准化,但CL更便于携带。 CL更大,因此无需外部库即可使用更多标准功能。这也意味着可以更轻松地完成更多与实现相关的事情。此外,Scheme还遭受着数以万亿计的实施,其中大多数实施都不兼容。这使CL非常可取。

图书馆员:与我的最后一点很相关。 Scheme有SRFI,但并未得到普遍认可。没有使用库的可移植方式。另一方面,CL确实有办法。 Quicklisp是来自上帝(Xach)的礼物-一种可供使用的库。没有真正的规范实现。另一方面,CL具有非常好的高性能或特定用途的实现(高性能:SBCL,商业:Allegro,嵌入式:ECL,可移植:CLISP,Java:ABCL,...)。

虽然我只在上面第一人称对话中讲话,但应该清楚我错过了什么,我没有错过。似乎您可能需要更多具体细节。帖子中有一些细节。]

评论


简短的预告摘要怎么样?^^

–戴夫O。
2011-3-20的3:35

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

–user1249
2011年3月20日在20:51

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

–正交
2011年3月21日在17:22

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

– Mark C
2011年4月6日在17:13

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

– David Thornley
2011年6月1日21:48

#3 楼

我最近使用一个具有C版本和Java版本的库启动了一个家庭项目。我想在项目中使用Lisp,在使用Common Lisp,Scheme或Clojure之间花费了大约一个月的时间。我对这三个项目都有一定的经验,但只有玩具项目。在告诉我最终选择哪一个之前,我将向您介绍一下我在每种情况下的经验。还可以让您键入方括号而不是括号,并在适当的地方将其切换回括号。 Racket在安装时还具有大量库,甚至可以下载更多库。可视调试器也很有用。

我的Common Lisp实现(SBCL)没有IDE,但是开放源代码CL实现习惯使用Emacs和SLIME。这种组合可能非常有效。除了在将表达式键入源文件时对表达式求值的能力之外,还有一个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方法,而不必包装它们。 (或者您是否还需要调用本机代码?)

–超级电动
2011年2月9日在21:49

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

–拉里·科尔曼(Larry Coleman)
2011年2月9日在22:08

#4 楼

我用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社区只是不相信并行性,而并行性对于该项目至关重要。我以为我可能会从球拍中错过的唯一一件事就是延续。但是,我可以通过使用重新启动来完成所需的操作,回想起来,很可能可以通过简单的关闭来完成它。

评论


我自己还没有尝试过,但是我已经看到了使用Emacs命令行球拍程序的人的博客文章。例如:bc.tech.coop/scheme/scheme-emacs.htm

–拉里·科尔曼(Larry Coleman)
2011年6月2日17:17

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

–雪橇
2014年9月15日21:00

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

–扔掉帐户
2015年4月6日下午5:58

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

–雪橇
2015年4月6日14:33

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

–扔掉帐户
2015年4月6日在16:55

#5 楼

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

评论


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

–orange80
2011-6-26 at 16:31

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

– SK-logic
2011年6月27日上午8:48

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

–雪橇
2014-09-15 21:02

@ArtB,我正在将eDSL实现为编译器功能,这可能会对它们的源AST起到很大的作用。在这种方法中,卫生是一个令人讨厌的事情。您可以看一下它是如何工作的:github.com/combinatorylogic/mbase

– SK-logic
2014年9月16日下午6:30