Lisp(歴史的には「リスト処理」の略語であるLISP )は、長い歴史を持ち、独特の完全に括弧で囲まれた接頭辞記法を持つプログラミング言語のファミリーです。[ 3 ] 元々は1950年代後半に仕様が定められ、Fortranに次いで現在も広く使われている2番目に古い高級プログラミング言語です。[ 4 ] [ 5 ] Lispは初期の頃から変化しており、その歴史の中で多くの方言が存在してきました。今日、最もよく知られている汎用Lisp方言は、Common Lisp、Scheme、Racket、Clojureです。[ 6 ] [ 7 ] [ 8 ]
Lispは、もともとコンピュータプログラムのための実用的な数学的記法として作成され、アロンゾ・チャーチのラムダ計算の記法に影響を受けた(ただし、元々はそこから派生したわけではない) [ 9 ] 。すぐに人工知能(AI)研究で好まれるプログラミング言語となった。[ 10 ]最も初期のプログラミング言語の1つとして、Lispはツリーデータ構造、自動ストレージ管理、動的型付け、条件分岐、高階関数、再帰、自己ホスト型コンパイラ[ 11 ]、および読み込み-評価-出力ループ[ 12 ]など、コンピュータサイエンスにおける多くのアイデアを開拓した。
LISPという名前は「リストプロセッサ」に由来します。[ 13 ]連結リストは Lisp の主要なデータ構造の 1 つであり、Lisp のソースコードはリストで構成されています。そのため、Lisp プログラムはソースコードをデータ構造として操作することができ、プログラマが Lisp に組み込まれた新しい構文や新しいドメイン固有言語を作成できるマクロシステムが生まれます。
コードとデータの相互交換性により、Lispはすぐに認識できる構文を実現しています。すべてのプログラムコードは、s式、つまり括弧で囲まれたリストとして記述されます。関数呼び出しまたは構文形式は、関数または演算子の名前が最初に来て、その後に引数が続くリストとして記述されます。たとえば、f3 つの引数を取る関数は、 のように呼び出されます。(farg1arg2arg3)
ジョン・マッカーシーは、 1958年にマサチューセッツ工科大学(MIT)に在籍中にLispの開発を始めました。彼は、 IBM 704で動作するAIプログラミング言語を作りたいという願望に駆り立てられていました。彼は、「IBMは人工知能の研究を精力的に進めるのに良い選択肢に見えた」と考えていたからです。[ 14 ]彼はリスト処理に基づく情報処理言語( IPL)に触発されましたが、IPLは異なるハードウェア向けに設計されていたため使用せず、代数言語の方が魅力的だと感じました。 [ 14 ]これらの要因から、彼はFortranライブラリとして実装されたFortranリスト処理言語の設計に助言しました。しかし、再帰や現代的なif-then-else文(Lispが最初に導入された当時は新しい概念でした)をサポートしていなかったため、彼はそれに不満を持っていました。[注1 ] [ 14 ]
マッカーシーのオリジナルの記法では、括弧で囲まれた「M式」が使用され、それがS式に変換されました。例として、M式はcar[cons[A,B]]S式と同等です。Lispが実装されると、プログラマーはすぐにS式を使用するようになり、M式は放棄されました。[ 14 ] M式は、ホレス・エネアによるMLisp [ 15 ]やヴォーン・プラットによるCGOLの短命な試みで再び登場しました。(car(consAB))
Lispは、パンチカードを使用してIBM 704コンピュータ上でスティーブ・ラッセルによって最初に実装されました。[ 16 ]ラッセルは当時マッカーシーの下で働いており、(マッカーシーを驚かせたことに)Lispのeval関数をマシンコードで実装できることに気づきました。
マッカーシー[ 17 ]によると
スティーブ・ラッセルは「このevalをプログラムしてみようか 」と言ったので、私は「おいおい、理論と実践を混同しているぞ。このevalは読み込み用であって、計算用ではないんだ」と答えた。しかし彼は結局やってのけた。つまり、私の論文にあるevalをIBM 704マシンコードにコンパイルし、バグを修正して、それをLispインタープリタとして宣伝したのだ。確かにそれはLispインタープリタだった。こうして、その時点でLispは基本的に今日のような形になったのだ。
その結果、Lispプログラムを実行できる、より正確には「Lisp式を評価できる」Lispインタープリタが完成した。
IBM 704用の2 つのアセンブリ言語マクロがリストを分解するための基本操作になりました。car (レジスタ番号のアドレス部分の内容) とcdr (レジスタ番号のデクリメント部分の内容) [ 18 ]。ここで「register」はコンピュータの中央処理装置(CPU) のレジスタを指します。Lisp の方言では、リストの最初の項目とリストの残りの項目をそれぞれ返す操作に、まだand ( / k ɑːr /と/ ˈ k ʊ d ər / ) を使用しています。carcdr
マッカーシーは、1960年4月1日にCommunications of the ACMに掲載された「記号式の再帰関数とその機械による計算、パートI」というタイトルの論文でLispの設計を発表しました。 [ 19 ] [ 20 ]彼は、いくつかの単純な演算子とチャーチから借用した匿名関数の記法を用いることで、アルゴリズムのためのチューリング完全な言語を構築できることを示しました。
Lispで書かれた最初の完全なLispコンパイラは、1962年にMITのTim HartとMike Levinによって実装され、既存のLISPインタプリタにコンパイラコードを解釈させるだけでコンパイルでき、インタプリタよりも40倍高速に実行できるマシンコード出力を生成することができました。 [ 21 ]このコンパイラは、コンパイルされた関数と解釈された関数を自由に混在させることができるLispのインクリメンタルコンパイルモデルを導入しました。HartとLevinのメモで使用されている言語は、McCarthyの初期のコードよりも現代のLispスタイルにずっと近いものです。
ゴミ収集ルーチンは、1962年以前にMITの大学院生ダニエル・エドワーズによって開発された。[ 22 ]
1980年代から1990年代にかけて、新しいLisp方言(主にMaclispの後継であるZetaLispやNIL(New Implementation of Lisp)など)の開発を単一の言語に統合する取り組みが精力的に行われた。こうして誕生した新しい言語、 Common Lispは、それまで使われていた方言とある程度の互換性を持っていた(書籍『Common Lisp the Language』では、様々な構成要素の互換性について言及されている)。1994年、ANSIはCommon Lispの標準規格「ANSI X3.226-1994 情報技術プログラミング言語 Common Lisp」を公表した。
Lispは誕生以来、特にPDP-10 [ 23 ]システムにおいて、人工知能研究コミュニティと密接な関係を築いてきました。Lispは、有名なAIシステムSHRDLUで使用された言語Micro Plannerの実装として使用されました。1970年代には、AI研究から商業的な派生製品が生まれるにつれ、プログラマーがLispの実装に関わるさまざまな技術や選択がパフォーマンスに及ぼす影響を理解する必要が生じたため、既存のLispシステムのパフォーマンスがますます重要な課題となりました。[ 24 ]
Lispは60年の歴史の中で、S式言語という基本テーマを基に数多くのバリエーションを生み出してきました。これらのバリエーションの中には、異なる優先順位を持つ様々なグループによって標準化され、実装されたものもあります(例えば、Common LispとSchemeには複数の実装が存在します)。しかし、ソフトウェアプロジェクトによっては、標準規格のないLispが定義され、方言と実装の明確な区別がない場合もあります(例えば、ClojureとEmacs Lispはこのカテゴリーに属します)。
方言(および/または実装)間の違いは非常に目立つ場合があります。たとえば、Common Lisp ではキーワードを使用して関数defunに名前を付けますが、Scheme では を使用しますdefine。[注 2 ]標準化された方言内では、準拠する実装は同じコア言語をサポートしますが、拡張機能とライブラリが異なります。これにより、基本言語から非常に目立つ変更が生じる場合もあります。たとえば、Guile(Scheme の実装)では、デフォルト引数やキーワード引数define*を持つことができる関数を作成するために を使用しますが、どちらも標準化されていません。


1990年代にやや衰退したLispは、2000年以降、再び注目を集めるようになった。新たな活動のほとんどは、Common Lisp、Scheme、Emacs Lisp、Clojure、Racketの実装に集中しており、移植性の高い新しいライブラリやアプリケーションの開発も含まれている。
多くの新しいLispプログラマーは、ポール・グラハムやエリック・S・レイモンドといった作家に触発され、他の人が時代遅れと考えていた言語を追求するようになった。新しいLispプログラマーは、この言語を目を見張るような経験だと表現し、他の言語よりもはるかに生産性が高いと主張することが多い。[ 37 ]この認識の高まりは、「AIの冬」と1990年代半ばのLispの一時的な隆盛とは対照的である。[ 38 ]
2010年現在当時、活発にメンテナンスされているCommon Lispの実装は11個あった。[ 39 ]
オープンソースコミュニティは、新しいサポート インフラストラクチャを作成しました。CLikiはCommon Lisp 関連の情報を収集する wiki であり、Common Lisp ディレクトリにはリソースが一覧表示され、#lisp は人気の IRC チャンネルで、コード スニペットの共有とコメントが可能です ( Lisp で書かれたIRC ボットlisppaste によるサポート付き)。Planet Lisp [ 40 ]はさまざまな Lisp 関連のブログのコンテンツを収集し、LispForum [ 41 ]ではユーザーが Lisp のトピックについて議論し、Lispjobs [ 42 ]は求人情報を告知するサービスであり、 Weekly Lisp Newsという週刊ニュース サービスもあります。Common -lisp.netはオープンソースの Common Lisp プロジェクトのホスティング サイトです。Quicklisp [ 43 ]は Common Lisp のライブラリ マネージャーです。
Lisp の 50 年 (1958 年~2008 年) は、LISP50@OOPSLA で祝われました。[ 44 ]ボストン、バンクーバー、ハンブルクでは定期的にローカルのユーザーミーティングが開催されています。その他のイベントには、European Common Lisp Meeting、European Lisp Symposium、International Lisp Conference などがあります。
Scheme コミュニティは、20 を超える実装を積極的に維持しています。2000 年代には、いくつかの重要な新しい実装 (Chicken、Gambit、Gauche、Ikarus、Larceny、Ypsilon) が開発されました。Schemeの標準であるRevised 5 Report on the Algorithmic Language Scheme [ 45 ]は、 Scheme コミュニティで広く受け入れられました。Scheme 実装要求プロセスにより、Scheme の準標準ライブラリと拡張機能が多数作成されました。個々の Scheme 実装のユーザー コミュニティは成長を続けています。2003 年に新しい言語標準化プロセスが開始され、2007 年に R 6 RS Scheme 標準が策定されました。コンピュータ サイエンスの教育における Scheme の学術的な使用は、やや減少しているようです。一部の大学では、コンピュータ サイエンス入門コースで Scheme を使用しなくなりました。[ 46 ] [ 47 ] MIT では、学部コンピュータ サイエンスプログラムと MITx 大規模公開オンライン コースで、Scheme の代わりにPythonを使用しています。[ 48 ] [ 49 ]
Lispには、 Arc、Hy、Nu、Liskell、LFE (Lisp Flavored Erlang)など、いくつかの新しい方言があります。Juliaのパーサーは、 Schemeの方言であるFemtolispで実装されています(JuliaはSchemeに影響を受けており、Scheme自体もLispの方言です)。
2019年10月、ポール・グラハムは「Lispの新しい方言」であるBelの仕様を公開した。
Common LispとSchemeは、Lisp開発における二つの主要な流れを代表する言語である。これらの言語は、設計上の選択において大きく異なる特徴を持っている。
Common LispはMaclispの後継です。主な影響源はLisp Machine Lisp、Maclisp、NIL、S-1 Lisp、Spice Lisp、Scheme です。[ 50 ] Lisp Machine Lisp ( Lisp マシンをプログラミングするために使用される大規模な Lisp 方言) の多くの機能を備えていますが、あらゆるパーソナル コンピュータまたはワークステーションで効率的に実装できるように設計されています。Common Lisp は汎用プログラミング言語であるため、多くの組み込みデータ型、関数、マクロ、その他の言語要素、オブジェクト システム ( Common Lisp オブジェクト システム) を含む大規模な言語標準を備えています。Common Lisp はまた、Scheme から字句スコープや字句クロージャなどの特定の機能を借用しています。 Common Lisp の実装は、LLVM [ 51 ]、Java仮想マシン[ 52 ]、 x86-64、PowerPC、Alpha、ARM、Motorola 68000、MIPS [ 53 ]などのさまざまなプラットフォーム、および Windows、macOS、Linux、Solaris、FreeBSD、NetBSD、OpenBSD、Dragonfly BSD、Heroku [ 54 ]などのオペレーティングシステムを対象として利用可能です。
Scheme は、 Guy L. Steele, Jr.とGerald Jay Sussmanによって発明された Lisp プログラミング言語の静的スコープと適切な末尾再帰の方言です。非常に明確でシンプルな意味論と、式を構成するさまざまな方法が少ないように設計されています。Common Lisp より約 10 年前に設計された Scheme は、よりミニマルな設計です。標準機能のセットははるかに小さいですが、Common Lisp では規定されていない特定の実装機能 (末尾呼び出し最適化や完全継続など) を備えています。命令型、関数型、メッセージパッシング スタイルなど、さまざまなプログラミング パラダイムが Scheme で便利に表現できます。Scheme は、一連の標準 (Revised n Report on the Algorithmic Language Scheme) と一連のScheme Requests for Implementationによって進化し続けています。
Clojureは Lisp の方言で、主にJava 仮想マシン、共通言語ランタイム(CLR)、Python VM、Ruby VM YARV、JavaScriptへのコンパイルを対象としています。実用的な汎用言語として設計されています。Clojure はHaskellから大きな影響を受けており、不変性を非常に重視しています。[ 55 ] Clojure は、オプションの型ヒントと型推論を使用して Java フレームワークとライブラリへのアクセスを提供し、Java への呼び出しでリフレクションを回避し、高速なプリミティブ操作を可能にします。Clojure は他の Lisp 方言との後方互換性を持つようには設計されていません。[ 56 ]
さらに、Lisp の方言は多くのアプリケーションでスクリプト言語として使用されており、最もよく知られているのはEmacsエディタのEmacs Lisp、AutoCADのAutoLISPおよび後のVisual Lisp 、 Audacityの Nyquist 、LilyPondの Schemeです。有用な Scheme インタープリタのサイズが小さい可能性があるため、組み込みスクリプトで特に人気があります。例としては、SIODとTinyScheme があり、どちらも「Script-fu」という一般的な名前でGIMP画像プロセッサにうまく組み込まれています。 [ 57 ]元々はEmacs Lisp言語に基づいて John Harper が作成した Lisp インタープリタである LIBREP は、Sawfishウィンドウマネージャに組み込まれています。[ 58 ]
Lispには、R6RS Scheme、R7RS Scheme、IEEE Scheme、[ 59 ] ANSI Common Lisp、ISO ISLISPといった公式に標準化された方言があります。
ポール・グラハムは、LispをFortranなどの既存の言語と区別する9つの重要な側面を特定しました。[ 60 ]
Lispは、プログラムコードの構造を標準データ構造に忠実かつ直接的に表現する最初の言語でした。この特性は後に「ホモイコニシティ」と呼ばれるようになりました。そのため、Lisp関数は、低レベルの操作を必要とせずに、Lispプログラム内で操作、変更、あるいは作成することができます。これは一般的に、Lispの表現力における主要な利点の1つと考えられており、構文マクロやメタ循環評価に適した言語となっています。
if-then-else構文を用いた条件文は、Fortranで書かれたチェスプログラムのためにマッカーシーによって考案された。彼はそれをALGOLに含めることを提案したが、 ALGOL 58仕様には含まれなかった。Lispでは、マッカーシーはより一般的なcond構造を使用した。[ 61 ] ALGOL 60はif-then-elseを採用し、普及させた。
Lisp は、ゼロックス PARCでSmalltalk を開発した研究チームのリーダーであるAlan Kay に大きな影響を与えました。そして、Lisp は Smalltalk の影響を受け、後の方言では 1970 年代にオブジェクト指向プログラミング機能 (継承クラス、カプセル化インスタンス、メッセージ パッシングなど) が採用されました。Flavorsオブジェクトシステムでは、多重継承とミックスインの概念が導入されました。Common Lisp オブジェクト システムでは、多重継承、多重ディスパッチによるマルチメソッド、および第一級の汎用関数が提供され、柔軟で強力な動的ディスパッチ形式が実現されています。これは、多くの後続 Lisp ( Schemeを含む) オブジェクト システムのテンプレートとして機能しており、多くの場合、メタオブジェクト プロトコルを介して実装されています。メタオブジェクト プロトコルとは、オブジェクト システムがそれ自身によって定義される、反射的なメタ循環設計です。Lisp は、Smalltalk に次いで、このようなメタオブジェクト システムを持つ 2 番目の言語でした (そして、今でもごく少数の言語の 1 つです)。何年も後、アラン・ケイは、これらの特徴が融合した結果、SmalltalkとLispだけが適切に設計されたオブジェクト指向プログラミングシステムとみなせると示唆した。[ 62 ]
Lispは自動ガベージコレクションの概念を導入しました。これは、システムがヒープを巡回して未使用のメモリを探すというものです。世代別ガベージコレクションなどの現代の高度なガベージコレクションアルゴリズムの進歩は、Lispでのその使用によって促進されました。[ 63 ]
エドガー・W・ダイクストラは1972年のチューリング賞受賞講演で次のように述べた。
LISPは、いくつかの非常に基本的な原理を基盤としているため、驚くべき安定性を示してきました。さらに、LISPは、ある意味で私たちの最も洗練されたコンピュータアプリケーションのかなりの数を担ってきました。LISPは冗談めかして「コンピュータを悪用する最も賢い方法」と表現されることがあります。私は、この表現は解放の真髄を伝えているので、素晴らしい褒め言葉だと思います。LISPは、これまで不可能だった思考を、私たちの最も才能ある仲間たちが考えるのを助けてきました。[ 64 ]
初期のコンピューティング ハードウェア (初期のマイクロプロセッサを含む) に関して、Lisp はリソース要件が高かったため、AIコミュニティ以外ではFortranやALGOL の流れを汲むC言語ほど普及しませんでした。複雑で動的なアプリケーションに適しているため、Lisp は 2010 年代に再び注目を集めました。[ 65 ]
Lispは式指向言語です。他の多くの言語とは異なり、「式」と「文」の区別はありません。すべてのコードとデータは式として記述されます。式が評価されると、値(場合によっては複数の値)が生成され、その値は他の式に埋め込むことができます。各値は任意のデータ型にすることができます。
マッカーシーの1958年の論文では、2種類の構文が紹介されました。1つは、コードとデータの内部表現を反映する記号式(S式、sexps)で、もう1つは、S式の関数を表現するメタ式(M式)です。M式は普及せず、現在ではほとんどすべてのLispがコードとデータの操作にS式を使用しています。
括弧の使用は、Lisp が他のプログラミング言語ファミリーと最も直接的に異なる点です。そのため、学生は長い間 Lisp にLost In Stupid ParenthesesやLots of Irritating Superfluous Parenthesesなどのニックネームを付けてきました。[ 66 ]しかし、S 式構文も Lisp のパワーの大部分を担っています。構文はシンプルで一貫性があり、コンピュータによる操作を容易にします。ただし、Lisp の構文は従来の括弧表記に限定されません。代替表記を含めるように拡張できます。たとえば、XMLisp は、メタオブジェクト プロトコルを使用してS 式を Extensible Markup Language ( XML ) と統合する Common Lisp 拡張機能です。
式への依存は、この言語に大きな柔軟性をもたらします。Lispの関数はリストとして記述されるため、データとまったく同じように処理できます。これにより、他のプログラムを操作するプログラム(メタプログラミング)を簡単に作成できます。多くのLisp方言は、マクロシステムを使用してこの機能を活用しており、言語をほぼ無制限に拡張できます。
Lispのリストは、要素を空白で区切り、括弧で囲んで記述します。たとえば、 は、要素が、、 および の3つの原子であるリストです。これらの値は暗黙的に型付けされており、それぞれ2つの整数と「シンボル」と呼ばれるLisp固有のデータ型であり、明示的に宣言する必要はありません。(12foo)12foo
空のリスト()も特別なアトムとして表現されますnil。これはLispにおいて、アトムとリストの両方の性質を持つ唯一の要素です。
式は、接頭辞表記を使用してリストとして記述されます。リストの最初の要素は、関数名、マクロ名、ラムダ式、または「特殊演算子」(下記参照)の名前です。リストの残りの部分は引数です。たとえば、関数はlist引数をリストとして返すため、式は次のようになります。
(リスト1 2 (引用foo ))リストに評価されます。前の例の の前の「引用符」は、引数を評価せずに返す「特殊演算子」です。引用符で囲まれていない式は、囲んでいる式が評価される前に再帰的に評価されます。たとえば、(12foo)foo
(リスト1 2 (リスト3 4 ))リストに評価されます。3 番目の引数はリストです。リストはネストできます。(12(34))
算術演算子も同様に扱われます。式
( + 1 2 3 4 )10 と評価されます。中置記法では、同等のものは " " になります。1+2+3+4
Lispには、 ALGOL派生言語で実装されているような演算子の概念はありません。Lispの算術演算子は可変引数関数(またはn項関数incf)であり、任意の数の引数を取ることができます。Cスタイルの「++」インクリメント演算子は、構文を与える名前で実装されることがあります。
( incf x )と同等で(setq x (+ x 1))、 の新しい値を返しますx。
「特殊演算子」(「特殊形式」とも呼ばれる)は、Lispの制御構造を提供します。たとえば、特殊演算子はif3つの引数を取ります。最初の引数がnilでない場合、2番目の引数に評価されます。そうでない場合は、3番目の引数に評価されます。したがって、式は
( nilの場合(リスト1 2 "foo" ) (リスト3 4 "bar" ))は と評価されます。もちろん、 の代わりに非自明な式が代入されていれば、これはより有用でしょう。(34"bar")nil
Lispには論理演算子and、or、notも用意されています。andとor演算子は短絡評価を行い、それぞれ最初の nil 引数と nil でない引数を返します。
(または(および"zero" nil "never" ) "James" 'task 'time )「ジェームズ」と評価されます。
もう1つの特殊演算子、lambdaは、変数を値にバインドし、その値を式内で評価するために使用されます。この演算子は関数を作成するためにも使用されます。の引数はlambda引数のリストと、関数が評価する式(戻り値は最後に評価された式の値です)です。
(ラムダ(引数) ( +引数1 ))ラムダ式は、適用されると引数を 1 つ受け取り、それをバインドして、その引数より 1 大きい数値を返す関数に評価されますarg。ラムダ式は名前付き関数と何ら変わりなく扱われ、同じように呼び出されます。したがって、式は
((ラムダ(引数) ( +引数1 )) 5 )は と評価されます6。ここでは、関数適用を行っています。匿名関数に値 5 を渡して実行します。
名前付き関数は、defunマクロを使用してラムダ式をシンボルに格納することによって作成されます。
( defun foo ( a b c d ) ( + a b c d ))(defunf(a)b...)fグローバル環境に新しい関数を定義します。これは概念的には次の式に似ています。
( setf ( fdefinition 'f ) #' ( lambda ( a ) ( block f b... )))はsetf、最初の引数の値を新しい関数オブジェクトに設定するために使用されるマクロです。は、 という名前の関数のグローバル関数定義です。は、関数オブジェクトを返す特殊演算子の略語です。fdefinition'ffdefinitionf#'function
オリジナルのLISPには、アトムとリストという2つの基本的なデータ型がありました。リストは要素の有限順序付きシーケンスであり、各要素はアトムまたはリストのいずれかで、アトムは数値またはシンボルでした。シンボルは基本的に一意の名前を持つ項目で、ソースコードでは英数字文字列として記述され、変数名として、または記号処理におけるデータ項目として使用されました。たとえば、リストには、シンボル、リスト、および数値2の3つの要素が含まれています。(FOO(BAR1)2)FOO(BAR1)

Lisp のリストは、単方向連結リストとして実装されています。[ 67 ]このリストの各セルはcons (Scheme ではペア) と呼ばれ、 carとcdrと呼ばれる2 つのポインタで構成されています。これらはそれぞれ、連結リストの記事で説明されているフィールドとフィールドに相当します。datanext
consセルから構築できる多くのデータ構造のうち、最も基本的なものの1つは「適切なリスト」と呼ばれます。適切なリストは、特別なnil(空のリスト)シンボル、または、がcarデータ(リストなどの別のcons構造である場合もある)を指し、がcdr別の適切なリストを指すconsのいずれかです。
特定の cons が連結リストの先頭であると仮定すると、その car はリストの最初の要素を指し、cdr はリストの残りの部分を指します。このため、連結リストの一部である cons (例えば、ツリーの一部ではない) を参照する場合、car関数は と ともcdr呼ばれます。firstrest
したがって、Lispのリストは、C++やJavaのコンテナクラスのインスタンスのようなアトミックオブジェクトではありません。リストは、連結されたconsの集合にすぎません。特定のリストを参照する変数は、リストの最初のconsへのポインタにすぎません。リストの走査は、リストをcdringで下方向に走査する(つまり、リストの各consを訪問するために連続してcdringを実行する)か、リストに対して関数をマッピングするいくつかの高階関数のいずれかを使用することによって行うことができます。
Lispシステムではコンスとリストが非常に普遍的であるため、これらがLispの唯一のデータ構造であるという誤解がよくあります。実際には、最も単純なLispを除けば、ベクトル(配列)、ハッシュテーブル、構造体など、他のデータ構造も存在します。

括弧で囲まれた S 式は、連結リスト構造を表します。同じリストを S 式として表現する方法はいくつかあります。cons はドット ペア表記でと書くことができます。ここでは car、cdr です。より長い適切なリストはドット ペア表記で書くことができます。これは慣習的にリスト表記でと略記されます。不適切なリスト[注 3 ]は、この 2 つの組み合わせで書くことができます。最後の cdr が である 3 つの cons のリスト(つまり、完全に指定された形式でのリスト) の場合などです。(a.b)ab(a.(b.(c.(d.nil))))(abcd)(abc.d)d(a.(b.(c.d)))
Lispには、リストへのアクセスや制御を行うための組み込み手続きが多数用意されています。リストはlist、任意の数の引数を受け取り、それらの引数のリストを返す手続きを使って直接作成できます。
(リスト1 2 'a 3 ) ;出力: (1 2 a 3)(リスト1 ' ( 2 3 ) 4 ) ;出力: (1 (2 3) 4)リストはconsペアから構成されるため、このcons手順はリストの先頭に要素を追加するために使用できます。consリストの構成方法のため、この手順はリスト引数の処理において非対称です。
( cons 1 ' ( 2 3 )) ;出力: (1 2 3)( cons ' ( 1 2 ) ' ( 3 4 )) ;出力: ((1 2) 3 4)このappend手順では、2つ(またはそれ以上)のリストを連結します。Lispのリストは連結リストであるため、2つのリストを連結する処理は漸近的に時間計算量が小さくなります。
( ' ( 1 2 ) ' ( 3 4 )を追加;出力: (1 2 3 4)( ' ( 1 2 3 ) ' () ' ( a ) ' ( 5 6 )) ;出力: (1 2 3 a 5 6)Lispのリストは単純な連結リストであるため、互いに構造を共有できます。つまり、2つのリストは同じ末尾、つまり最後のconsのシーケンスを持つことができます。たとえば、次のCommon Lispコードを実行した後です。
( setf foo ( list 'a 'b 'c )) ( setf bar ( cons 'x ( cdr foo )))リストfooと はそれぞれとbarです。ただし、どちらのリストでも末尾の構造は同じです。コピーではありません。 と を指す cons セルは、どちらのリストでも同じメモリ位置にあります。(abc)(xbc)(bc)bc
構造をコピーするのではなく共有することで、パフォーマンスを劇的に向上させることができます。ただし、この手法は、引数として渡されたリストを変更する関数と意図しない形で相互作用する可能性があります。たとえば、あるリストをc別のリストに置き換えるなどして変更するとgoose、他のリストにも影響が及びます。
( setf ( third foo ) 'goose )これはfooに変わりますが、それによって もに変わります。これは予期しない結果となる可能性があります。これはバグの原因となる可能性があり、引数を変更する関数は、まさにこの理由から破壊的であると文書化されています。(abgoose)bar(xbgoose)
関数型プログラミングの愛好家は破壊関数を避けます。関数型スタイルを好む Scheme 方言では、破壊関数の名前には注意を促す感嘆符、つまり「bang」が付けられます。例えば、set-car!( set car bangと読みます) は cons の car を置き換えます。Common Lisp 方言では、破壊関数は一般的です。 に相当する関数は「replace car」とset-car!名付けられていますrplaca。ただし、Common Lisp には破壊関数の定義と使用を容易にする特別な機能 が含まれているためsetf、この関数はめったに見られません。Common Lisp でよく見られるスタイルは、プロトタイプ作成時に関数型 (破壊呼び出しなし) でコードを記述し、安全な場所で最適化として破壊呼び出しを追加することです。
Lisp は、ユーザーが入力した式を評価します。シンボルとリストは、別の(通常はより単純な)式に評価されます。たとえば、シンボルは、それが名前を付ける変数の値に評価されます。はに評価されます。ただし、他のほとんどの形式は、それ自体に評価されます。Lispに入力すると、が返されます。(+23)555
式は、評価されないようにマークすることもできます(シンボルやリストの場合に必要です)。これは、quote特殊演算子、またはその省略形'(引用符1つ)の役割です。たとえば、通常、シンボル を入力するとfoo、対応する変数の値が返されます(そのような変数がない場合はエラーが返されます)。リテラルシンボルを参照するには、または、通常は と入力します。(quotefoo)'foo
Common Lisp と Scheme の両方で、バッククォート演算子 ( Scheme では準クォートと呼ばれます) が`文字 (バッククォート) で入力されます。これは通常のクォートとほぼ同じですが、式を評価し、その値をカンマアンクォート演算子とカンマアットスプライス演算子を使用して引用リストに挿入することができます。変数の値が の場合、 はに評価され、 は に評価されます。バッククォートは、マクロ展開を定義する際によく使用されます。[ 68 ] [ 69 ],,@snue(barbaz)`(foo,snue)(foo(barbaz))`(foo,@snue)(foobarbaz)
自己評価形式と引用形式は、Lispにおけるリテラルに相当するものです。プログラムコード内で(可変な)リテラルの値を変更できる場合があります。例えば、関数が引用形式を返し、その関数を呼び出すコードがその形式を変更すると、後続の呼び出しにおける関数の動作が変わる可能性があります。
( defun should-be-constant () ' ( one two three ))( let (( stuff ( should-be-constant ))) ( setf ( third stuff ) 'bizarre )) ; bad!(定数であるべき) ; (1 2 奇妙な値を返す)このように引用された形式を変更することは、一般的に悪いスタイルとみなされており、ANSI Common Lisp では誤りであると定義されています (コンパイルされたファイルでは、ファイルコンパイラが類似の定数を結合したり、書き込み保護されたメモリに配置したりする可能性があるため、「未定義」の動作が発生します)。
Lispにおける引用の形式化は、ダグラス・ホフスタッター(『ゲーデル、エッシャー、バッハ』 )らによって、自己参照という哲学的概念の一例として指摘されている。
Lispファミリーは、動的スコープと静的(別名:レキシカル)スコープのどちらを使用するかで分かれています。Clojure、Common Lisp、Schemeはデフォルトで静的スコープを使用しますが、newLISP、Picolisp 、 EmacsおよびAutoCADに組み込まれている言語は動的スコープを使用します。Emacsはバージョン24.1以降、動的スコープとレキシカルスコープの両方を使用しています。
Lispと他の言語との根本的な違いは、Lispではプログラムのテキスト表現は、基盤となるLispシステムで使用されるものと同じ内部データ構造(リンクリスト、シンボル、数値、文字など)を人間が読めるように記述したものであるという点です。
Lispはこれを利用して非常に強力なマクロシステムを実装しています。Cプリプロセッサ(C、Objective-C、C++プログラミング言語用のマクロプリプロセッサ)で定義されているマクロ言語と同様に、マクロはコンパイル可能なコードを返します。ただし、Cプリプロセッサのマクロとは異なり、マクロはLisp関数であるため、Lispの持つ力を最大限に活用できます。
さらに、Lispコードはリストと同じ構造を持つため、言語内のリスト処理関数を用いてマクロを作成できます。つまり、Lispがデータ構造に対して行える操作はすべて、Lispマクロでコードに対して行うことができます。これに対し、他のほとんどの言語では、パーサーの出力は言語実装内部に限定されており、プログラマーが操作することはできません。
この機能により、言語内で効率的な言語を容易に開発できます。例えば、Common Lispオブジェクトシステムは、マクロを使用して言語拡張として簡潔に実装できます。つまり、アプリケーションが異なる継承メカニズムを必要とする場合、別のオブジェクトシステムを使用できます。これは、他のほとんどの言語とは大きく異なります。例えば、Javaは多重継承をサポートしておらず、それを追加する合理的な方法もありません。
単純なLispの実装では、このリスト構造が直接解釈されてプログラムが実行されます。関数は文字通りリスト構造の一部であり、実行時にインタプリタによって走査されます。しかし、ほとんどの本格的なLispシステムにはコンパイラも含まれています。コンパイラはリスト構造を機械語またはバイトコードに変換して実行します。このコードは、Cなどの従来の言語でコンパイルされたコードと同等の速度で実行できます。
マクロはコンパイルステップの前に展開されるため、いくつかの興味深いオプションが提供されます。プログラムが事前に計算されたテーブルを必要とする場合、マクロはコンパイル時にテーブルを作成することができ、コンパイラはテーブルを出力するだけでよく、実行時にテーブルを作成するコードを呼び出す必要はありません。Lisp の実装の中には、eval-whenコンパイル時 (マクロが必要とする時) にはコードが存在するが、出力されるモジュールには存在しないことを可能にするメカニズムさえあります。[ 70 ]
Lisp言語は、対話型のコマンドラインと併用されることが多く、統合開発環境(IDE)と組み合わせて使用される場合もあります。ユーザーはコマンドラインで式を入力するか、IDEに指示してLispシステムに式を送信します。Lispは入力された式を読み込み、評価し、結果を出力します。このため、Lispのコマンドラインは読み込み・評価・出力ループ(REPL)と呼ばれます。
REPLの基本的な動作は以下のとおりです。これは簡略化した説明であり、引用符やマクロなど、実際のLispの多くの要素は省略されています。
このread関数はテキスト形式のS式を入力として受け取り、それを内部データ構造に解析します。例えば、プロンプトにテキストを入力すると、この関数はそれを3つの要素(記号、数値1、数値2)を持つ連結リストに変換します。偶然にも、このリストは有効なLispコードでもあります。つまり、評価可能です。これは、リストのcarが関数名(加算演算)を表しているためです。(+12)read+
Afooは単一の記号として読み取られます。123は数字の 123 として読み取られます。"123"は文字列 "123" として読み取られます。
このeval関数はデータを評価し、結果として0個以上のLispデータを返します。評価は必ずしも解釈を意味するわけではありません。Lispシステムによっては、すべての式をネイティブマシンコードにコンパイルするものもあります。しかし、評価を解釈として説明するのは簡単です。carが関数名であるリストを評価するには、evalまずcdrで指定された各引数を評価し、次にその引数に関数を適用します。この場合、関数は加算であり、引数リストに適用すると答えが得られます。これが評価の結果です。(12)3
このシンボルはfoo、シンボル foo の値に評価されます。文字列 "123" のようなデータも同じ文字列に評価されます。このリストは、リスト (1 2 3) に評価されます。(quote(123))
関数の役割は、printユーザーに結果を表示することです。3このような単純な結果であれば、処理は簡単です。リスト構造の一部に評価される式の場合は、printリストを走査してS式として出力する必要があります。
Lisp REPL を実装するには、これら 3 つの関数と無限ループ関数を実装するだけで済みます。(当然ながら、 の実装は、や などevalの特殊演算子もすべて実装する必要があるため、複雑になります。)これが完了すれば、基本的な REPL は 1 行のコードになります。iflambda(loop(print(eval(read))))
Lisp REPLは通常、入力編集機能、入力履歴機能、エラー処理機能、およびデバッガへのインターフェースも提供する。
Lispは通常、即時評価されます。Common Lispでは、引数は適用順序(「左端が最も内側」)で評価されますが、 Schemeでは引数の順序は未定義であり、コンパイラによる最適化の余地が残されています。
Lispには当初、制御構造がほとんどありませんでしたが、言語の進化の過程で多くの制御構造が追加されました。(Lispの初期の条件演算子である`$_`はcond、後の制御構造の先駆けとなりましたif-then-else。)
Scheme 方言のプログラマーは、末尾再帰を使用してループを表現することがよくあります。Scheme が学術的なコンピュータ サイエンスでよく使われるため、一部の学生は、Lisp で反復を記述する唯一の方法、または最も一般的な方法は末尾再帰であると信じていますが、これは間違いです。よく見かける Lisp 方言はすべて、Scheme のdoループからCommon Lispの複雑な式まで、命令型の反復構造を持っています。さらに、これが主観的な問題ではなく客観的な問題となる重要な点は、Scheme が末尾呼び出しloopの処理に関して特定の要件を定めていることであり、したがって、Scheme で末尾再帰の使用が一般的に推奨される理由は、その慣行が言語定義によって明示的にサポートされているためです。対照的に、ANSI Common Lisp は、末尾呼び出しの削除と呼ばれる最適化を要求していません[ 71 ]。したがって、Common Lisp では、より伝統的な反復構造 ( 、 、など)のカジュアルな代替として末尾再帰スタイルを使用することが推奨されていない[ 72 ]のは、単なるスタイルの好みの問題ではなく、効率性 (Common Lisp では明らかな末尾呼び出しが単純なジャンプとしてコンパイルされない可能性があるため) とプログラムの正しさ (末尾再帰は Common Lisp でスタックの使用を増加させ、スタック オーバーフローのリスクを高める可能性があるため) の問題である可能性があります。dodolistloop
Lispの制御構造の中には、他の言語の構文キーワードに相当する特殊な演算子があります。これらの演算子を用いた式は、表面上は関数呼び出しと同じように見えますが、引数が必ずしも評価されるとは限らない点、あるいは反復式の場合は複数回評価される可能性がある点で異なります。
他の主要なプログラミング言語のほとんどとは異なり、Lispでは言語自体を使って制御構造を実装することができます。いくつかの制御構造はLispマクロとして実装されており、動作原理を知りたいプログラマーはマクロ展開を行うことも可能です。
Common LispとSchemeはどちらも、非ローカル制御フローのための演算子を備えています。これらの演算子の違いは、両言語の最も大きな違いの一つです。Schemeは、プロシージャを使用して再入継続call/ccをサポートしており、これによりプログラムは実行中の特定の場所を保存(および後で復元)できます。Common Lispは再入継続をサポートしていませんが、エスケープ継続を処理するためのいくつかの方法をサポートしています。
多くの場合、同じアルゴリズムをLispでは命令型スタイルと関数型スタイルの両方で表現できます。前述のように、Schemeは関数型スタイルを好む傾向があり、末尾再帰と継続を使用して制御フローを表現します。しかし、命令型スタイルも十分に可能です。多くのCommon Lispプログラマーが好むスタイルは、Cなどの構造化言語に慣れているプログラマーにとってより馴染み深いものに感じられるかもしれませんが、Schemeプログラマーが好むスタイルは、Haskellなどの純粋関数型言語により近いものとなっています。
Lispはリスト処理を初期から担ってきた言語であるため、シーケンスの反復処理に関する高階関数を豊富に備えています。他の言語(forC言語のループなど)では明示的なループが必要となるような場合でも、Lispでは高階関数を用いることで同じ処理を実現できます。(これは多くの関数型プログラミング言語にも当てはまります。)
map良い例として、Scheme では、Common Lisp では と呼ばれる関数がありますmapcar。 関数と 1 つ以上のリストが与えられた場合、mapcar関数をリストの要素に順番に適用し、結果を新しいリストに収集します。
(マップカー#' + ' ( 1 2 3 4 5 ) ' ( 10 20 30 40 50 ))これは、+対応するリスト要素のペアそれぞれに関数を適用し、結果を生成します。(1122334455)
以下はCommon Lispコードの例です。
基本的な「Hello, World!」プログラム:
( 「こんにちは、世界!」と表示)Lispの構文は再帰処理に非常に適しています。再帰的に定義された集合の列挙などの数学的な問題は、この記法で簡単に表現できます。例えば、ある数の階乗を計算するには、次のように記述します。
( defun階乗( n ) ( if ( zerop n ) 1 ( * n (階乗( 1- n )))))代替実装では、基盤となるLispシステムが末尾再帰を最適化する場合、以前のバージョンよりもスタック領域が少なくて済みます。
( defun factorial ( n &optional ( acc 1 )) ( if ( zerop n ) acc ( factorial ( 1 -n ) ( * acc n ))))上記の例を、Common Lispのloopマクロを使用した反復バージョンと比較してみましょう。
( defun factorial ( n ) ( loop for i from 1 to n for fac = 1 then ( * fac i ) finally ( return fac )))以下の関数はリストを反転させます。(Lispの組み込み関数である反転関数も同じ機能を持っています。)
( defun -reverse ( list ) ( let (( return-value )) ( dolist ( e list ) ( push e return-value )) return-value ))Lisp の上に、Lisp と並行して、または Lisp に組み込まれたさまざまなオブジェクト システムとモデルが構築されています。
言語ベースのシステムを含むいくつかのオペレーティングシステムは、Lisp をベースにしている(Lisp の機能、規約、メソッド、データ構造などを使用している)か、Lisp で記述されている。[ 75 ]以下はその例である。
Genera(後にOpen Generaと改名)[ 76 ]はSymbolics社によるもの。MedleyはInterlispで書かれ、元々はXerox社の後期Starワークステーションで動作していたグラフィカルオペレーティングシステムのファミリーだった[ 77 ] [ 78 ] 。Mezzano [ 79 ] 。Interim [ 80 ] [ 81 ] 。Tao Systems社のTAOSの開発者によるChrysaLisp [ 82 ] [ 83 ]、そしてGNU/Linux用のGuix Systemもある。
(defun f (x) x)Scheme:(define f (lambda (x) x))または(define (f x) x)#n=(...)複数の参照を持つ対象の cons セルを表すために を使用し、#n#この cons を参照するために使用されます。たとえば、(#1=(a b) . #1#)は通常 (循環構造の印刷が有効になっていない場合) として印刷されます((a b) a b)が、 を使用すると cons セルの再利用が明確になります。 は#1=(a . #1#)循環しているため、通常は印刷できませんが、(a...)が表示される場合があり、 で定義された cons セルの CDR は#1=自身です。は四半世紀ほど使われ続けてきた生き残り言語である。現在も使われているプログラミング言語の中で、これより長く使われているのはFortranだけだ。
最も重要かつ魅力的な言語の一つがLISP(「リスト処理」の略)です。LISPは、Algolが発明された頃にジョン・マッカーシーによって開発されました。その後、LISPは人工知能分野の研究者の間で非常に人気を博しています。
プロジェクトは 1963 年初頭に 24 ビット マシンとして開始された。設計目標であった LISP 用に 36 ビットに拡張された。
は後方互換性に制約されないLispである。
私は 1957 年~ 1958 年にかけて MIT で IBM 704 用に FORTRAN で書いた一連のチェスの合法的な動きルーチンに関連して条件式を発明しました
...条件式を定義し、Algol での使用を提案する論文が Communications of the ACM に送られましたが、非常に短かったため、恣意的に編集者への手紙に格下げされました。
当時、私はLISPの「実体メタ言語」という巨大なアイデアを理解していませんでしたが、拡張可能な言語に関するアイデアにはある程度近づきました
... この第2段階は、最終的にLISPを理解し、その理解を使用して、より見やすく、より小さく、より強力で、より遅延バインディングされたアンダー構造を作成することでした
... 私にとってOOPとは、メッセージング、ローカル保持、状態プロセスの保護と隠蔽、およびすべてのものの極端な遅延バインディングのみを意味します。これはSmalltalkとLISPで可能です。おそらくこれが可能な他のシステムもありますが、私はそれらを知りません。