web-dev-qa-db-ja.com

スキームと一般的なLISP:プロジェクトに違いをもたらした特性は何ですか?

StackOverflowとこのサイトの両方で、漠然とした "Scheme vs Common LISP"の質問が不足しているわけではないので、これにさらに焦点を当てたいと思います。質問は両方の言語でコーディングした人向けです:

Schemeでコーディングしているときに、Common LISPコーディングの経験で最も見逃した要素は何ですか?または、逆に、Common LISPでのコーディング中に、Schemeでのコーディングで何を逃しましたか?

言語機能だけを意味するわけではありません。以下は、質問に関する限り、見逃してはならない有効な事項です。

  • 特定のライブラリ。
  • SLIME、DrRacketなどの開発環境の特定の機能.
  • CコードのブロックをSchemeソースに直接書き込むGambitの機能など、特定の実装の機能。
  • そしてもちろん、言語機能。

私が期待している種類の回答の例:

  • 「私はCommon LISPにXを実装しようとしていましたが、Schemeのファーストクラスの継続があったとしても、完全にYを実行するだけでしたが、代わりにZを実行する必要がありました。
  • 「Schemeプロジェクトでのビルドプロセスのスクリプト作成は、ソースツリーが大きくなり、Cライブラリをリンクするにつれて次第に痛みが増しました。次のプロジェクトでは、Common LISPに戻りました。」
  • 「私は既存の大規模なC++コードベースを持っています。私にとって、C++呼び出しをGambitスキームコードに直接埋め込むことができたことは、SWIGサポートの欠如を含め、Schemeが一般的なLISPに持つ可能性のある欠点に完全に値するものでした。」

ですから、「スキームはより単純な言語」などの一般的な感情ではなく、戦争の物語を望んでいます。

159
SuperElectric

私の学士号は、認知科学と人工知能でした。それから、LISPへの1コースのイントロがありました。その言語は(「エレガント」のように)興味深いと思いましたが、後でグリーンスパンの第10規則に出会うまでは、あまりそのことを考えていませんでした。

十分に複雑なCまたはFortranプログラムには、Common LISPの半分のアドホックで非公式に指定された、バグが多く、遅い実装が含まれています。

Greenspunの要点は、(部分的には)多くの複雑なプログラムにインタープリターが組み込まれているということでした。インタープリターを言語に組み込むよりも、インタープリター(またはコンパイラー)がすでに組み込まれているLISPのような言語を使用する方が理にかなっている可能性があると彼は提案しました。

当時、私はカスタム言語のカスタムインタープリターを使用してユーザー定義の計算を実行するかなり大きなアプリに取り組んでいました。大規模な実験として、LISPでコアを書き直すことにしました。

およそ6週間かかりました。元のコードはDelphi(Pascalバリアント)の約10万行でした。 LISPでは約10,000行に削減されました。しかし、さらに驚くべきことは、LISPエンジンが3〜6倍高速であるという事実でした。これはLISPの初心者の作品であることを忘れないでください!その全体の経験は私にとってかなり目を見張るものでした。初めて、パフォーマンスと表現力を単一の言語で組み合わせる可能性を見ました。

しばらくして、ウェブベースのプロジェクトに取り組み始めたとき、私は多くの言語を試聴しました。 LISPとSchemeをミックスに含めました。最後に、Schemeの実装-- Chez Scheme を選択しました。結果には非常に満足しています。

Webベースのプロジェクトは、高性能 "選択エンジン" です。データの処理からデータのクエリ、ページの生成まで、さまざまな方法でSchemeを使用しています。多くの場所で、実際には別の言語から始めましたが、以下で簡単に説明する理由により、Schemeに移行することになりました。

今、私はあなたの質問に(少なくとも部分的に)答えることができます。

オーディションでは、さまざまなLISPとSchemeの実装を調べました。 LISP側では、Allegro CL、CMUCL、SBCL、およびLispWorksを(私は信じて)調べました。 Scheme側では、Bigloo、Chicken、Chez、Gambitを(私は信じて)見ました。 (言語の選択はずっと前でした。そのため、私は少しかすんでいます。もしそれが重要であるなら、いくつかのメモを掘り下げることができます。)

私たちが探していたのは、a)ネイティブスレッド、b)Linux、Mac、Windowsのサポートです。これらの2つの条件を組み合わせたため、AllegroとChez以外のすべてのユーザーがノックアウトされたため、評価を続けるには、マルチスレッドの要件を緩和する必要がありました。

一連の小さなプログラムをまとめて、評価とテストに使用しました。それは多くの問題を明らかにしました。たとえば、一部の実装には欠陥があり、一部のテストの実行を完了できませんでした。一部の実装では、実行時にコードをコンパイルできませんでした。一部の実装では、ランタイムコンパイル済みコードとプリコンパイル済みコードを簡単に統合できませんでした。一部の実装には、他の実装よりも明らかに良い(または明らかに悪い)ガベージコレクターがありました。等.

私たちのニーズでは、3つの商用実装(Allegro、Chez、およびLispworks)のみが主要なテストに合格しました。 3つのうちChezだけがすべてのテストに合格しました。当時、私はLispworksがどのプラットフォームにもネイティブスレッドを持っていなかったと思います(今はそうだと思います)。Allegroは、一部のプラットフォームにのみネイティブスレッドを持っていたと思います。さらに、Allegroには "call us"ランタイムライセンス料がかかりましたが、私はあまり好きではありませんでした。 Lispworksには実行時の料金がなく、Chezには簡単な(そして非常に合理的な)構成があった(そして、実行時にコンパイラーを使用した場合にのみ有効になった)と思います。

LISPとSchemeの両方でいくらか重要なコードのチャンクを生成したことは、いくつかの比較と対照のポイントです。

  • LISP環境ははるかに成熟しています。あなたはお金のためにより多くの強打を得る。 (そうは言っても、コードが増えるとバグも増えることになります。)

  • LISP環境の学習ははるかに困難です。熟練するまでにはもっと多くの時間が必要です。 Common LISPは巨大な言語です-そしてそれは、商用実装がその上に追加するライブラリに到達する前です。 (そうは言っても、Schemeの構文ケースはLISPのどの場合よりもはるかに微妙で複雑です。)

  • LISP環境では、バイナリを生成するのがいくらか難しい場合があります。不要なビットを削除するには、イメージを「シェイク」する必要があります。そのプロセス中にプログラムを正しく実行しないと、後でランタイムエラーが発生する可能性があります。 。対照的に、Chezでは、必要な他のすべてのファイルを含むトップレベルのファイルをコンパイルして完了です。

以前、Schemeを当初は意図していなかった多くの場所で使用することになったと述べました。どうして?頭の上の3つの理由が考えられます。

まず、Chez(およびその開発者であるCadence)を信頼することを学びました。私たちはツールから多くのことを尋ねました、そしてそれは一貫して届けられました。たとえば、Chezには歴史的にわずかな数の欠陥があり、そのメモリマネージャーは非常に優れています。

第二に、シェから得たパフォーマンスを愛することを学びました。私たちはスクリプト言語のように感じられるものを使用しており、そこからネイティブコードの速度を得ていました。重要ではないいくつかのことについては、しかしそれは決して害を及ぼさず、時にはそれは恐ろしい多くのことを助けました。

3番目に、Schemeが提供できる抽象化を愛することを学びました。ちなみに、私は単にマクロを意味しているのではありません。クロージャー、ラムダ、末尾呼び出しなどのことを意味します。これらの用語で考え始めると、他の言語は比較するとかなり制限されているように見えます。

スキームは完璧ですか?番号;それはトレードオフです。 1つ目は、個々の開発者がより効果的になることです。ただし、ほとんどの言語が持っている標識(forループなど)がSchemeにないため(たとえば、100万通りの方法があるため) forループ)。次に、話したり、雇ったり、借りたりする開発者のプールがはるかに少なくなります。

要約すると、LISPとSchemeは他のどこでも広く利用できないいくつかの機能を提供します。その機能はトレードオフであるため、特定のケースで意味のあるものであることが望ましいです。私たちの場合、LISPとSchemeのどちらを採用するかを決定する要素は、言語やライブラリの機能よりも、非常に基本的な機能(プラットフォームサポート、プラットフォームスレッド、ランタイムコンパイル、ランタイムライセンス)に関係しています。繰り返しになりますが、これもトレードオフでした。Chezを使用すると、必要なコア機能を利用できましたが、商用LISP環境にあった広範なライブラリが失われました。

また、繰り返しになりますが、私たちはずっと前にさまざまなLispとスキーマを調べました。それ以来、それらはすべて進化し、改善されてきました。

102

私は通常、リンクを回答として貼り付けるのは好きではありませんが、これについてブログ記事を書きました。網羅的ではありませんが、いくつかの主要なポイントを通過します。

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

Edit:主なポイントは次のとおりです。

  1. [〜#〜]存在[〜#〜]:両方のlispsが他のlispsの束の後に来ました。スキームは最小限の公理的な経路をとった。 CLはバロック様式のルートを取りました。
  2. [〜#〜] case [〜#〜]:通常、Schemeは大文字と小文字を区別します。 CLはそうではありません(可能です)。これは見逃されることもありますが、その実用性については(私が)議論しています。
  3. [〜#〜] names [〜#〜]:CL内のシンボルの名前は、多くの場合、奇妙で混乱を招きます。 TERPRIPROGNなど。Schemeは通常、非常に意味のある名前を持っています。これはCLで見逃されているものです。
  4. [〜#〜]関数[〜#〜]:CLには個別の関数名前空間があります。これは、Schemeでは見逃せません。単一の名前空間を使用すると、通常は非常にクリーンな関数型プログラミングが可能になります。これは、CLでは困難または扱いにくい場合があります。ただし、コストがかかります。Schemeでは、「list」から「lst」などの名前を難読化する必要がある場合があります。
  5. [〜#〜]マクロ[〜#〜]:Schemeで最も低レベルのダーティマクロが見つかりません。うん、syntax-rulesは、本当にいくつかのことをハックしたいまでは、すべてうまくいきます。一方、CLでは衛生的なマクロが見落とされることがあります。それらを行うための標準的な方法がないことは、車輪を再発明することを意味します。
  6. [〜#〜]移植性[〜#〜]:両方の言語が標準化されているにもかかわらず、CLはより移植性が高い場合がよくあります。 CLの方が大きいため、外部ライブラリなしで使用できる標準機能が多くあります。また、より多くの実装に依存することが移植可能にできることも意味します。また、Schemeには1兆の実装があり、そのほとんどは多少互換性がありません。これはCLを非常に望ましいものにします。
  7. [〜#〜]ライブラリ[〜#〜]:最後のポイントに非常に関連しています。 SchemeにはSRFIがありますが、広く認められているわけではありません。ライブラリを操作するポータブルな方法はありません。一方、CLには方法があります。そしてQuicklispはgod(Xach)からの贈り物です---使用するためのライブラリの一種のリポジトリ。
  8. [〜#〜]実装[〜#〜]:Schemeは、非常に多くの実装があることに悩まされています。実際の標準的な実装はありません。一方、CLには、非常に優れた高性能または特定用途向けの実装があります(高性能:SBCL、商用:Allegro、埋め込み:ECL、ポータブル:CLISP、Java:ABCLなど)。

私は少しだけ一人称で話をしましたが、私が何を逃して何を欠いていないかは明らかです。

[これらが一般的すぎる場合は、謝罪します。もっと具体的な詳細が必要なようです。投稿にはいくつかの詳細があります。]

37
Quadrescence

最近、CバージョンとJavaバージョンのライブラリを使用してホームプロジェクトを開始しました。プロジェクトにLISPを使用したかったので、Common LISP、SchemeまたはClojure。私は3つすべてである程度の経験がありますが、おもちゃのプロジェクトのみです。どのプロジェクトを選択したのかを説明する前に、それぞれについての私の経験について少しお話します。

PLTラケットには、ニースIDEがあり、エディターから式を評価できるだけでなく、括弧の代わりに角かっこを入力して、必要に応じて括弧に戻すこともできます。ラケットにも大きなセットがあります。ライブラリがインストールされていて、さらにダウンロードが可能です。ビジュアルデバッガーも役立ちます。

私のCommon LISP実装(SBCL)にはIDEがありませんが、オープンソースのCL実装では、EmacsとSLIMEを使用するのが通例です。この組み合わせは非常に効率的です。ソースファイルに式を入力するときに式を評価する機能に加えて、emacsのすべての編集コマンドを使用できるREPLもあるので、コードのコピーは両方の方法で効率的に行えます。 REPLバッファに表示されるオブジェクトは、コピーして貼り付けることができます。Alt+(およびAlt+)は、一致する括弧とインデントを処理するのに効率的です。

上記のEmacs機能はすべてClojureでも使用できます。 Clojureでの編集経験は、LISPのそれと似ています。 Java interopは問題なく動作しました。完成したらClojureプロジェクトを実行したいと思います。

3つすべて(Common LISP、Racket、Clojure)を使用してライブラリにアクセスできましたが、プロジェクトにCommon LISPを選択することになりました。決定的な要因は、FFIがCommon LISPではるかに使いやすいことでした。 CFFIには、サンプルコードと各方法の詳細な説明を含む非常に優れたマニュアルがあります。午後に20個のC関数をラップすることができ、それ以来コードに触れる必要はありませんでした。

もう1つの要因は、ClojureやR6RSスキームよりも、一般的なLISPに慣れていることです。 Practical Common LISPとGrahamの本のほとんどを読んだことがあり、Hyperspecに慣れています。まだ「lispy」なコードではありませんが、経験を積むにつれて、それは変わると確信しています。

25
Larry Coleman

CLとラケットの両方でプログラミングします。

私は現在Common LISPでWebサイトを開発しており、ラケットの以前の雇用主のために社内プログラムのスイートを作成しました。

社内コードについては、雇用主がWindowsショップであり、LispWorksの支払いをすることができなかったため、ラケット(当時はPLTスキームと呼ばれていました)を選択しました。 Windows用の唯一のgood Windows用のオープンソースCL実装はCCLであり(現在もそうです)、これにはプロセッサでSSEのサポートが必要です。石器時代のハードウェア。雇用主がまともなハードウェアを持っていたとしても、Common LISPでの結果の唯一のGUIライブラリは、Unixでのみ機能するMcCLIMです。プロジェクトの成功。

私は原始的なDrRacketエディターを我慢して1年以上過ごしました。 EMACSは、MrEdと呼ばれていたGUIバージョンのラケットをWindowsの下位LISPに変えることができませんでした。カーソル位置の式を1回のキーストロークで評価できなくてはなりませんでした。代わりに、S式を手動で選択してコピーし、REPLウィンドウに切り替えます(それに切り替えるためのキーストロークがないため))、S式を貼り付けます。私が使用していた関数またはマクロの予想される引数を表示できるエディターなしで実行する必要がありました。DrRacketはSLIMEの代わりにはなりません。

雇用主は、複雑なXML APIを備えた独自のデータベースを使用していましたが、SELECTクエリのバージョンに応答するには、一見不要な情報のロードが必要でした。私はHTMLPragを使用して、このAPIにXMLを出力することと、応答を解析することの両方を行うことにしました。それはうまくいきました。

SQLのように見えるフォームを入力して過度に複雑なXML APIと対話できるマクロを書くために、ラケットの過度に複雑な「構文ケース」マクロシステムを学ぶ必要がありました。 DEFMACROを自由に使えるようにしておけば、この部分はずっと簡単だったでしょう。しかし、それを達成するためにより多くの労力を費やしたにもかかわらず、最終結果は依然としてシームレスでした。

さらに、私はCommon LISPのLOOPマクロなしでやらなければなりませんでした。私がほとんどのコードを書いた後でラケットが代替案を提供し始めました、そしてその代替案はLOOPに比べてまだ悪いです(ラケットの開発チームはそれがより良いと主張していますが、それらは単に間違っています)。リストを反復するために "car"と "cdr"を使用する名前付きのLETフォームをたくさん書いてしまいました。

Carとcdrについて言えば、Schemeによる(car '())のエラーとしての解釈ほど苛立たしいものはありません。私はラケットの大文字と小文字の区別を利用し、共通のLISPのセマンティクスを持つCARとCDRを実装しました。ただし、 '()と#fを分離することで、デフォルト値として'()を返すことの有用性が大幅に低下します。

UNWIND-PROTECTを再実装することにもなり、ラケットが残したギャップを埋めるために独自の再起動システムを発明しました。ラケットコミュニティは、再起動が非常に便利で、実装が簡単であることを学ぶ必要があります。

ラケットのlet-valuesフォームは過度に冗長だったので、MULTIPLE-VALUE-BINDを実装しました。ラケット必須あなたはallを使用するかどうかにかかわらず、生成される値を受け取るため、これは絶対に必要でした。

その後、Common LISPでeBay XML APIクライアントを作成しようとしましたが、HTMLPragのようなものがないことがわかりました。 HTMLPragは非常に便利です。私はそのプロジェクトをラケットでやることになりました。私は、ラケットの文芸的プログラミング施設を実験しましたが、私が地球上で、適切に書かれた文芸的コードを通常のコードよりも編集が難しい、または不適切に書かれた「過剰なコメント」文芸的コードを見つけたプログラマーであることがわかりました。

私の新しいプロジェクトはCommon LISPで行われています。これは、ラケットコミュニティがこのプロジェクトに不可欠な並列処理を信じていないため、正しい選択でした。私がラケットから逃したかもしれないと私が思った唯一のものは継続でした。しかし、私は再起動を使用することで必要なことを行うことができ、振り返ってみると、おそらく単純なクロージャーでそれを行うことができたでしょう。

21
Racketeer

Schemeは別のコンパイルを念頭に置いて設計されています。結果として、そのマクロの能力は、貧弱で制限のある衛生的なマクロシステムの代わりにCommon LISPスタイルのdefmacroを許可する拡張機能を使用しても、厳しく制限されることがよくあります。別のマクロを定義するマクロを定義することが常に可能であるとは限らず、次のコード行ですぐに使用することを目的としています。そして、そのような可能性は、効率的なeDSLコンパイラを実装するために不可欠です。

言うまでもなく、R5RSのハイジェニックマクロのみを使用したSchemeの実装は、私にとってメタプログラミングスタイルを適切に衛生に変換できないため、ほとんど役に立ちません。

幸いにも、その制限がないSchemeの実装(例:ラケット)があります。

5
SK-logic