Common Lisp ( CL ) はLisp プログラミング言語の方言であり、米国規格協会(ANSI) の標準文書ANSI INCITS 226-1994 (S2018) [ 1 ] (旧X3.226-1994 (R1999) ) に掲載されています。[ 2 ]ハイパーリンク付きのHTMLバージョンであるCommon Lisp HyperSpec は、ANSI Common Lisp 標準から派生したものです。[ 3 ]
Common Lisp 言語は、MacLispの標準化され改良された後継として開発されました。1980 年代初頭までに、Lisp Machine Lisp (別名 ZetaLisp)、Spice Lisp、NIL、S-1 Lispなど、MacLisp のさまざまな後継の開発に取り組んでいたグループがいくつかありました。Common Lisp は、これらの MacLisp 方言の機能を統一、標準化、拡張することを目指しました。Common Lisp は実装ではなく、言語仕様です。[ 4 ] Common Lisp 標準の実装は、フリーでオープンソースのソフトウェアやプロプライエタリ製品を含め、いくつか利用可能です。[ 5 ] Common Lisp は汎用マルチパラダイムプログラミング言語です。手続き型、関数型、オブジェクト指向のプログラミングパラダイムの組み合わせをサポートしています。動的プログラミング言語として、反復コンパイルによって効率的な実行時プログラムを生成する、進化的で漸進的なソフトウェア開発を容易にします。この漸進的な開発は、実行中のアプリケーションを中断することなく対話的に行われることがよくあります。
また、オプションの型注釈と型キャストもサポートしており、必要に応じて後のプロファイリングおよび最適化段階で追加することで、コンパイラがより効率的なコードを生成できるようになります。たとえば、ハードウェアと実装でサポートされている範囲内の非ボックス化fixnum整数を保持できるため、大きな整数や任意精度型よりも効率的な算術演算が可能になります。同様に、最適化宣言を使用することで、モジュールごとまたは関数ごとに、必要な安全レベルの種類をコンパイラに指示できます。
Common Lispには、マルチメソッドとメソッドの組み合わせをサポートするオブジェクトシステムであるCLOSが含まれています。これは多くの場合、メタオブジェクトプロトコルを用いて実装されます。
Common Lispは、 Lispマクロ(コード変換)やリーダーマクロ(文字の入力解析器)といった標準機能によって拡張可能です。
Common Lisp はMaclispおよび John McCarthy のオリジナルのLispとの部分的な後方互換性を提供します。これにより、古い Lisp ソフトウェアを Common Lisp に移植することができます。[ 6 ]
Common Lisp の開発は、ARPA マネージャーの Bob Engelmore が単一のコミュニティ標準 Lisp 方言を開発するというイニシアチブをとった後、1981 年に開始されました。[ 7 ]初期の言語設計の多くは電子メールで行われました。[ 8 ] [ 9 ] 1982 年に、Guy L. Steele Jr. が、 1982 年 ACM Symposium on LISP and functional programming で Common Lisp の最初の概要を発表しました。[ 10 ]
最初の言語ドキュメントは、1984 年にCommon Lisp the Language (CLtL1 として知られる) 初版として発行されました。1990 年に発行された第 2 版 (CLtL2 として知られる) では、ANSI Common Lisp 標準化プロセス中に行われた言語への多くの変更が組み込まれました。拡張 LOOP 構文、Common Lisp オブジェクト システム、エラー処理のための条件システム、プリティ プリンタへのインターフェースなどです。しかし、CLtL2 は最終的な ANSI Common Lisp 標準を記述していないため、ANSI Common Lisp のドキュメントではありません。最終的な ANSI Common Lisp 標準は、1994 年に発行されました。それ以降、標準の更新は発行されていません。Common Lisp へのさまざまな拡張と改善 (Unicode、並行処理、CLOS ベースの IO など) は、実装やライブラリによって提供されています。
Common LispはLispの方言です。コードとデータ構造の両方を表すのにS式を使用します。関数呼び出し、マクロ形式、特殊形式は、演算子の名前を先頭にしたリストとして記述します。例を以下に示します。
( + 2 2 )は、2と2を足して4を返します。関数名は「+」です。Lispには、このような演算子はありません。( defvar *x* ) ; 変数 *x* が存在することを保証しますが、; 値を与えません。アスタリスクは、慣例として特別な (グローバル) 変数を示す名前の一部です。 ; シンボル *x*には、 ; 後続のバインディングが字句的ではなく動的であるという特性も付与されます。 ( setf *x* 42.1 ) ; 変数 *x* に浮動小数点値 42.1 を設定します。;; 数値を二乗する関数を定義します: ( defun square ( x ) ( * x x ));; 関数を実行します: ( square 3 ) ; 9 を返します;; 'let'構文はローカル変数のスコープを作成します。ここでは、;; 変数'a'は6に、変数'b'は4にバインドされています。 'let'の中には「本体」があり、最後に計算された値が返されます。;; ここでは、'let'式からaとbを加算した結果が返されます。 ;; シンボルが特殊変数としてマークされていない限り(たとえば、以前のDEFVARによって)、変数aとbはレキシカルスコープを持ちます。 ( let (( a 6 ) ( b 4 )) ( + a b )) ; 10を返しますCommon Lispには多くのデータ型があります。
数値型には、整数、比率、浮動小数点数、複素数があります。[ 11 ] Common Lisp では、任意のサイズと精度の数値を表すためにbignum を使用します。比率型は分数を正確に表しますが、これは多くの言語では利用できない機能です。Common Lisp は、これらの型間で数値を適切に自動的に変換します。
Common Lispの文字型はASCII文字に限定されません。最新の実装のほとんどはUnicode文字をサポートしています。[ 12 ]
シンボル型は Lisp 言語に共通ですが、それ以外の言語ではほとんど知られていません。シンボルは、名前、値、関数、プロパティ リスト、パッケージなど、いくつかの部分を持つ一意の名前付きデータ オブジェクトです。これらのうち、値セルと関数セルが最も重要です。Lisp のシンボルは、他の言語の識別子と同様に、変数の値を保持するために使用されることが多いですが、他にも多くの用途があります。通常、シンボルが評価されると、その値が返されます。一部のシンボルは、自身に評価されます。たとえば、キーワード パッケージのすべてのシンボルは自己評価されます。Common Lisp のブール値は、自己評価シンボル T と NIL で表されます。Common Lisp には、シンボルの名前空間があり、「パッケージ」と呼ばれます。
スカラー数値をさまざまな方法で丸めるための関数がいくつか用意されています。 関数はround引数を最も近い整数に丸め、中間値の場合は偶数に丸めます。truncate、floor、関数はceilingそれぞれゼロ、切り捨て、切り上げに丸めます。これらの関数はすべて、切り捨てられた小数部分を二次値として返します。たとえば、(floor -2.5)は −3、0.5 を返し、(ceiling -2.5)は −2、−0.5 を返し、(round 2.5)は 2、0.5 を返し、 は(round 3.5)4、−0.5 を返します。
Common Lispにおけるシーケンス型には、リスト、ベクトル、ビットベクトル、文字列などがあります。あらゆるシーケンス型に対して適用できる操作が多数存在します。
他のほとんどすべてのLisp方言と同様に、Common Lispのリストはcons (consセルまたはconsペアとも呼ばれる)で構成されます。consはcarとcdrと呼ばれる2つのスロットを持つデータ構造です。リストはconsの連結チェーン、または空のリストです。各consのcarはリストのメンバー(別のリストの場合もある)を参照します。各consのcdrは次のconsを参照します。ただし、リストの最後のconsのcdrはnil値を参照します。consはツリーやその他の複雑なデータ構造を実装するためにも簡単に使用できますが、通常は構造体またはクラスのインスタンスを使用することが推奨されます。consを使用して循環データ構造を作成することも可能です。
Common Lisp は多次元配列をサポートしており、必要に応じて調整可能な配列を動的にサイズ変更できます。多次元配列は行列演算に使用できます。ベクトルは一次元配列です。配列は任意の型をメンバーとして保持できます (同じ配列内に複数の型が混在していても可)。また、ビットのベクトルのように、特定の型のメンバーのみを含むように特化することもできます。通常、サポートされている型はごく少数です。多くの実装では、使用する配列が型特化されている場合に配列関数を最適化できます。2 つの型特化配列型が標準です。文字列は文字のベクトルであり、ビットベクトルはビットのベクトルです。
ハッシュテーブルは、データオブジェクト間の関連付けを格納します。任意のオブジェクトをキーまたは値として使用できます。ハッシュテーブルは必要に応じて自動的にサイズ変更されます。
パッケージはシンボルの集合であり、主にプログラムの各部分を名前空間に分割するために使用されます。パッケージは一部のシンボルをエクスポートし、それらをパブリックインターフェースの一部としてマークすることができます。パッケージは他のパッケージを使用することもできます。
構造体は、 C言語の構造体やPascal言語のレコードと同様に、任意の数と型のフィールド(スロットと呼ばれる)を持つ任意の複雑なデータ構造を表します。構造体は単一継承を可能にします。
クラスは構造体と似ていますが、より動的な機能と多重継承を提供します(CLOSを参照)。クラスはCommon Lispに後から追加されたため、構造体と概念的に重複する部分があります。クラスから作成されたオブジェクトはインスタンスと呼ばれます。ジェネリック関数は特殊なケースです。ジェネリック関数は関数であると同時にインスタンスでもあります。
Common Lispは第一級関数をサポートしています。例えば、他の関数を引数として受け取る関数や、関数を返す関数を作成することが可能です。これにより、非常に汎用的な操作を記述することができます。
Common Lispライブラリは、このような高階関数に大きく依存しています。例えば、この関数は、引数として関係演算子sortと、オプションのキーワード引数としてキー関数を受け取ります。これは、あらゆる種類のデータをソートするだけでなく、キーに基づいてデータ構造をソートするためにも使用できます。
;; 関係演算子として > と < 関数を使用してリストをソートします。( sort ( list 5 2 6 3 1 4 ) #' > ) ; 結果: (6 5 4 3 2 1) ( sort ( list 5 2 6 3 1 4 ) #' < ) ; 結果: (1 2 3 4 5 6);; 各サブリストの最初の要素に基づいてリストをソートします。( sort ( list ' ( 9 A ) ' ( 3 B ) ' ( 4 C )) #' < :key #' first ) ; 結果: ((3 B) (4 C) (9 A))関数の評価モデルは非常に単純です。評価器が形式に遭遇すると(f a1 a2...)、f という名前の記号は次のいずれかであると想定します。
lambda。fが関数名の場合、引数はa1, a2, ..., an左から右の順に評価され、関数が見つかり、それらの値がパラメータとして渡されて呼び出されます。
このマクロはdefun関数を定義します。関数定義では、関数名、引数名、および関数本体が指定されます。
( defun square ( x ) ( * x x ))関数定義には、コンパイラに最適化設定や引数のデータ型に関するヒントを提供するコンパイラ指示(宣言とも呼ばれる)が含まれる場合があります。また、Lispシステムが対話型ドキュメントを提供するために使用するドキュメント文字列(docstring)が含まれる場合もあります。
( defun square ( x ) "単精度浮動小数点数 x の二乗を計算します。" ( declare ( single-float x ) ( optimize ( speed 3 ) ( debug 0 ) ( safety 1 ))) ( the single-float ( * x x )))匿名関数(関数リテラルlambda)は、式を用いて定義されます。例えば、引数を二乗する関数などがこれに該当します。Lispプログラミングスタイルでは、高階関数が頻繁に使用されますが、その際に匿名関数を引数として指定すると便利です。(lambda (x) (* x x))
fletローカル関数は、およびを使用して定義できますlabels。
( flet (( square ( x ) ( * x x ))) ( square 3 ))関数の定義や操作に関連する演算子は他にもいくつかあります。例えば、関数はcompile特定の演算子を使ってコンパイルできます。(Lispシステムの中には、コンパイルするように指示されない限り、デフォルトでインタプリタを使って関数を実行するものもあれば、すべての関数をコンパイルするものもあります。)
このマクロは汎用関数defgenericを定義します。汎用関数はメソッドの集合です。このマクロはメソッドを定義します。defmethod
メソッドは、CLOS標準クラス、システムクラス、構造体クラス、または個々のオブジェクトに対して、そのパラメータを特殊化できます。多くの型には、対応するシステムクラスが存在します。
汎用関数が呼び出されると、多重ディスパッチによって実際に使用するメソッドが決定されます。
( defgeneric add ( a b ))( defmethod add (( a number ) ( b number )) ( + a b ))( defmethod add (( a vector ) ( b number )) ( map 'vector ( lambda ( n ) ( + n b )) a ))( defmethod add (( a vector ) ( b vector )) ( map 'vector #' + a b ))( defmethod add (( a string ) ( b string )) ( concatenate 'string a b ))( add 2 3 ) ; 5 を返します( add #( 1 2 3 4 ) 7 ) ; #(8 9 10 11) を返します( add #( 1 2 3 4 ) #( 4 3 2 1 )) ; #(5 5 5 5) を返します( add "COMMON " "LISP" ) ; "COMMON LISP" を返しますジェネリック関数もまた、第一級データ型です。ジェネリック関数とメソッドには、上記で説明した以外にも多くの機能があります。
Common LispとSchemeの重要な違いは、Common Lispの関数名の名前空間がデータ変数の名前空間とは別になっている点です。Common Lispでは、関数の名前空間で名前を定義する演算子には、、、、、などdefunがありfletます。labelsdefmethoddefgeneric
これは、変数と関数をそれぞれ独自の名前空間に分割したジョン・マッカーシーのLisp 1.5で確立されたパターンに従っています。ジョン・マッカーシーのオリジナルのLisp言語では、両方に対して単一の名前空間しかありませんでした。[ 13 ]
Common Lisp 関数を名前で別の関数の引数として渡すには、function特殊演算子 を使用する必要があります。この演算子は、一般的に と略されます#'。上記の最初のsort例では、関数名前空間内の というシンボルで指定された関数>(コード )を参照しています。逆に、このように渡された関数を呼び出すには、引数に対して 演算子を使用します。#'>funcall
Schemeの評価モデルはよりシンプルです。名前空間は1つしかなく、引数だけでなく、フォーム内のすべての位置が(任意の順序で)評価されます。そのため、一方の言語で書かれたコードは、もう一方の言語に精通したプログラマーにとっては混乱を招くことがあります。例えば、多くのCommon Lispプログラマーは、listやstringといった分かりやすい変数名を好んで使いますが、Schemeではこれらの変数名が関数名をローカルに隠してしまうため、問題が生じる可能性があります。
関数に別の名前空間を設けることが利点かどうかは、Lisp コミュニティで議論の的となっている。これは通常、Lisp-1 対 Lisp-2 論争と呼ばれている。Lisp-1 は Scheme のモデルを指し、Lisp-2 は Common Lisp のモデルを指す。これらの名称は、1988 年にRichard P. GabrielとKent Pitmanが発表した論文で考案されたもので、この論文では 2 つのアプローチが詳細に比較されている。[ 13 ]
Common Lisp は、複数の値の概念をサポートしています[ 14 ]。これは、どの式にも常に単一の主値がありますが、関心のある呼び出し元が受け取って検査できる任意の数の副値も持つことができるというものです。この概念はリスト値を返すこととは異なり、副値は完全にオプションであり、専用のサイドチャネルを介して渡されます。つまり、呼び出し元は、副値が必要ない場合は、副値が存在することに全く気づかないままでいられる可能性があり、場合によっては役立つが必ずしも必要ではない情報を伝達するためのメカニズムを便利に使用できます。たとえば、
TRUNCATEは、与えられた数値をゼロ方向の整数に丸めます。ただし、余りも二次値として返すため、どの値が切り捨てられたかを簡単に判断できます。また、オプションの除数パラメータもサポートしており、これを使用してユークリッド除算を簡単に実行できます。( let (( x 1266778 ) ( y 458 )) ( multiple-value-bind ( quotient remainder ) ( truncate x y ) ( format nil "~A を ~A で割った値は ~A で、余りは ~A" x y quotient remainder )));;;; => "1266778 ÷ 458 は 2765 余り 408"GETHASH[ 16 ] は、連想マップのキーの値、それ以外の場合はデフォルト値、および値が見つかったかどうかを示す二次ブール値を返します。したがって、値が見つかったかデフォルト値として提供されたかを気にしないコードは、そのまま使用できますが、そのような区別が重要な場合は、二次ブール値を調べて適切に反応することができます。どちらの使用例も同じ呼び出しでサポートされ、どちらも不必要に負担や制約を受けることはありません。この機能が言語レベルで用意されているため、他の言語で行われるようにnull( defun get-answer ( library ) ( gethash 'answer library 42 ))( defun the-answer-1 ( library ) ( format nil "答えは ~A です" ( get-answer library ))) ;;;; ANSWER が LIBRARY に存在しない場合、"答えは 42" を返します( defun the-answer-2 ( library ) ( multiple-value-bind ( answer sure-p ) ( get-answer library ) ( if ( not sure-p ) "I don't know" ( format nil "The answer is ~A" answer )))) ;;;; ANSWER が LIBRARY に存在しない場合は "I don't know" を返します複数の値はいくつかの標準形式でサポートされており、最も一般的なのは、MULTIPLE-VALUE-BIND二次値にアクセスしたり、VALUES複数の値を返すための特別な形式です。
( defun magic-eight-ball () "見通し予測を返します。確率は二次値です" ( values "見通し良好" ( random 1.0 )));;;; => "見通し良好" ;;;; => 0.3187Common Lispのその他のデータ型には以下が含まれます。
他の多くのプログラミング言語と同様に、Common Lispプログラムも変数、関数、その他多くの種類のエンティティを参照するために名前を使用します。名前付き参照はスコープの制約を受けます。
名前と、その名前が指し示す実体との間の関連付けを、バインディングと呼ぶ。
範囲とは、ある名称が特定の拘束力を持つと判断される一連の状況を指します。
Common Lispにおけるスコープを決定する状況には、以下のようなものがあります。
(go x)ラベル に制御を移すことを意味しますxが、 は(print x)変数 を参照しますx。タグ本体ラベルは変数名とは別の名前空間にあるため、 のスコープはどちらもxプログラムテキストの同じ領域でアクティブにすることができます。特殊形式またはマクロ形式は、その構文内のすべてのシンボルの意味を完全に制御します。たとえば、(defclass x (a b) ())クラス定義である では、 は(a b)基底クラスのリストであるため、これらの名前はクラス名の空間で検索され、 は既存のバインディングへの参照ではなく、とxから派生する新しいクラスの名前です。これらの事実は、 のセマンティクスからのみ生じます。この式に関する唯一の一般的な事実は、 がマクロバインディングを参照しているということです。その他すべては に委ねられています。abdefclassdefclassdefclassxなどのバインディング構造で囲まれている場合、その参照はそのバインディングによって作成されたスコープ内にあります。letxシンボルが何を指しているのかを理解するには、Common Lisp プログラマーは、どのような種類の参照が表現されているのか、それが変数参照の場合はどのようなスコープ(動的スコープか字句スコープか)を使用しているのか、そして実行時の状況(参照が解決される環境、バインディングが環境に導入された場所など)を知る必要があります。
Lispには、グローバルに遍在する環境がいくつか存在する。例えば、新しい型が定義されると、その後はどこでもその型が認識される。その型への参照は、このグローバルな環境内でその型を検索する。
Common Lisp の環境の一種に、動的環境があります。この環境で確立されたバインディングは動的範囲を持ちます。つまり、バインディングはletブロックなどの構造の実行開始時に確立され、その構造の実行が終了すると消滅します。その有効期間は、ブロックの動的な有効化と無効化に連動しています。ただし、動的バインディングはブロック内だけでなく、そのブロックから呼び出されるすべての関数からも参照可能です。このような可視性は、不定スコープと呼ばれます。動的範囲(有効期間がブロックの有効化と無効化に連動)と不定スコープ(そのブロックから呼び出されるすべての関数から参照可能)の両方を持つバインディングは、動的スコープを持つと言われます。
Common Lisp は、特殊変数とも呼ばれる動的スコープ変数をサポートしています。再起動や catch タグなど、他の種類のバインディングも必然的に動的スコープになります。関数バインディングはflet(字句スコープの関数バインディングのみを提供する) を使用して動的スコープにすることはできませんが、関数オブジェクト (Common Lisp の第一レベルオブジェクト) は動的スコープ変数に割り当て、動的スコープで を使用してバインドし、その後、またはletを使用して呼び出すことができます。funcallAPPLY
動的スコープは、グローバル変数に参照の明確さと規律をもたらすため、非常に有用です。コンピュータサイエンスでは、グローバル変数は潜在的なエラーの原因として好ましくないものとされています。なぜなら、グローバル変数はモジュール間で場当たり的で隠れた通信経路を生み出し、望ましくない予期せぬ相互作用を引き起こす可能性があるからです。
Common Lispでは、トップレベルバインディングのみを持つ特殊変数は、他のプログラミング言語のグローバル変数と全く同じように動作します。新しい値を格納でき、その値はトップレベルバインディングの値を単純に置き換えます。グローバル変数の値を不用意に置き換えることは、グローバル変数の使用によって引き起こされるバグの根本原因です。しかし、特殊変数を扱う別の方法として、式の中で新しいローカルバインディングを与える方法があります。これは、変数の「再バインディング」と呼ばれることもあります。動的スコープの変数をバインディングすると、その変数用に一時的に新しいメモリ位置が作成され、その場所に名前が関連付けられます。このバインディングが有効な間は、その変数へのすべての参照は新しいバインディングを参照し、以前のバインディングは隠蔽されます。バインディング式の実行が終了すると、一時的なメモリ位置は消滅し、元の値を保持したまま古いバインディングが表示されます。もちろん、同じ変数に対して複数の動的バインディングをネストすることも可能です。
マルチスレッドをサポートする Common Lisp の実装では、動的スコープは実行スレッドごとに固有です。そのため、特殊変数はスレッドローカルストレージの抽象化として機能します。あるスレッドが特殊変数を再バインドしても、この再バインドは他のスレッドのその変数には影響しません。バインディングに格納された値は、そのバインディングを作成したスレッドのみが取得できます。各スレッドが何らかの特殊変数をバインドすると*x*、*x*スレッドローカルストレージのように動作します。を再バインドしないスレッド間では*x*、通常のグローバル変数のように動作します。つまり、これらのスレッドはすべて、同じトップレベルのバインディングを参照します*x*。
動的変数を使用すると、実行コンテキストにコンテキスト情報を追加して拡張できます。この情報は、関数パラメータとして明示的に指定することなく、関数間で暗黙的に渡されます。これは、制御の転送が無関係なコードの層を通過する必要があり、追加データを渡すためのパラメータを追加できない場合に特に役立ちます。このような状況では、通常、グローバル変数が必要になります。このグローバル変数は、再帰時にスキームが壊れないように保存および復元する必要があります。動的変数の再バインドはこの処理を行います。また、この変数はスレッドローカルにする必要があります(そうしないと、大きなミューテックスを使用する必要があります)。これは、スレッド時にスキームが壊れないようにするためです。動的スコープの実装は、この処理も行います。
Common Lispライブラリには、多くの標準特殊変数があります。例えば、すべての標準入出力ストリームは、よく知られた特殊変数のトップレベルバインディングに格納されています。標準出力ストリームは、*standard-output*に格納されています。
関数 foo が標準出力に書き込むと仮定します。
( defun foo () ( format t "Hello, world" ))標準出力の出力を文字列として取得するには、文字列ストリームにバインドして次のように呼び出すことができます。
( with-output-to-string ( *標準出力* ) ( foo ))-> "Hello, world" ; 収集された出力は文字列として返されます
Common Lispはレキシカル環境をサポートしています。正式には、レキシカル環境内のバインディングはレキシカルスコープを持ち、名前空間の種類に応じて、不定範囲または動的範囲のいずれかになります。レキシカルスコープとは、可視性がバインディングが確立されたブロック内に物理的に制限されることを意味します。そのブロックにテキスト的に(つまりレキシカルに)埋め込まれていない参照は、そのバインディングを参照できません。
TAGBODY 内のタグはレキシカル スコープを持ちます。式 (GO X) は、ラベル X を含む TAGBODY に埋め込まれていない場合はエラーになります。ただし、ラベルのバインディングは動的範囲を持つため、TAGBODY の実行が終了すると消滅します。そのコード ブロックがレキシカル クロージャの呼び出しによって再度実行される場合、そのクロージャの本体が GO を介してタグに制御を移そうとすることは無効です。
( defvar *stashed* ) ;; 関数を保持します( tagbody ( setf *stashed* ( lambda () ( go some-label ))) ( go end-label ) ;; スキップ (print "Hello") some-label ( print "Hello" ) end-label ) -> NILTAGBODYが実行されると、まずsetf形式が評価され、関数が特殊変数*stashed*に格納されます。次に、(go end-label)によって制御がend-labelに移り、コード(print "Hello")がスキップされます。end-labelはtagbodyの末尾にあるため、tagbodyは終了し、NILが返されます。ここで、以前に記憶された関数が呼び出されたとします。
( funcall *stashed* ) ;; エラー!この状況は誤りです。ある実装では、「GO: タグ SOME-LABEL のタグボディは既に終了しています」というメッセージを含むエラー状態が返されます。この関数は、タグボディに字句的に埋め込まれた (go some-label) を評価しようとし、ラベルに解決します。しかし、タグボディは実行されておらず (その範囲が終了しているため)、制御の転送は行われません。
Lisp のローカル関数バインディングはレキシカルスコープを持ち、変数バインディングもデフォルトでレキシカルスコープを持ちます。GO ラベルとは対照的に、これらはどちらも無限の範囲を持ちます。レキシカル関数または変数バインディングが確立されると、そのバインディングを確立した構造が終了した後でも、参照が可能である限り、そのバインディングは存在し続けます。レキシカルクロージャのおかげで、確立構造が終了した後でも、レキシカル変数と関数への参照が可能になります。
字句的バインディングは、Common Lisp 変数のデフォルトのバインディング モードです。個々のシンボルについては、ローカル宣言またはグローバル宣言のいずれかによって動的スコープに切り替えることができます。後者は、DEFVAR や DEFPARAMETER のような構造を使用することで暗黙的に発生する場合があります。Common Lisp プログラミングでは、特殊 (つまり動的スコープ) 変数の名前がアスタリスク記号で始まり、アスタリスク記号で終わるという重要な慣習があり、これは「イヤーマフ*規約」と呼ばれています。[ 17 ]この規約に従うと、特殊変数用に別の名前空間が効果的に作成され、字句的であるはずの変数が誤って特殊変数にならないようになります。
語彙範囲は、いくつかの理由から有用である。
まず、実行時環境の構造が比較的単純なため、変数や関数への参照を効率的なマシンコードにコンパイルできます。多くの場合、スタックストレージに最適化できるため、レキシカルスコープの開閉に伴うオーバーヘッドは最小限に抑えられます。完全なクロージャを生成する必要がある場合でも、クロージャの環境へのアクセスは効率的です。通常、各変数はバインディングのベクトルへのオフセットとなり、変数参照はベースアドレスとオフセットアドレス指定モードによる単純なロード命令またはストア命令になります。
第二に、語彙スコープ(不定範囲と組み合わせたもの)は語彙閉包を生み出し、それが関数を第一級オブジェクトとして使用することを中心としたプログラミングのパラダイム全体を生み出し、それが関数型プログラミングの根幹を成す。
第三に、おそらく最も重要な点として、たとえレキシカルクロージャが利用されなくても、レキシカルスコープの使用によってプログラムモジュールが不要な相互作用から隔離されます。レキシカル変数は可視性が制限されているため、プライベートです。モジュール A がレキシカル変数 X をバインドし、別のモジュール B を呼び出す場合、B 内の X への参照が A でバインドされた X に誤って解決されることはありません。B は単に X にアクセスできないだけです。変数を介した規律ある相互作用が望ましい状況のために、Common Lisp は特殊変数を提供します。特殊変数を使用すると、モジュール A は、A から呼び出される別のモジュール B から見える変数 X のバインドを設定できます。これができることは利点であり、それを防ぐことができることも利点です。したがって、Common Lisp はレキシカルスコープと動的スコープの両方をサポートしています。
Lispにおけるマクロは、使用方法において関数と表面上は似ています。しかし、評価される式を表すのではなく、プログラムのソースコードの変換を表します。マクロは、自身が囲むソースコードを引数として受け取り、それらをパラメータにバインドして、新しいソース形式を計算します。この新しい形式もマクロを使用できます。マクロ展開は、新しいソース形式がマクロを使用しなくなるまで繰り返されます。最終的に計算された形式が、実行時に実行されるソースコードとなります。
Lispにおけるマクロの典型的な使用例:
以下のような、さまざまな標準Common Lisp機能もマクロとして実装する必要があります。
setf代入演算子/アクセス演算子のコンパイル時展開をカスタマイズできるようにする標準抽象化with-accessors、、その他同様with-slotsのマクロwith-open-fileWITHifまたはcond他のマクロに基づいて構築された特殊演算子であり、whenマクロunlessで構成されます。loopドメイン固有言語マクロはdefmacroマクロによって定義されます。特殊演算子macrolet を使用すると、ローカル(字句スコープ)マクロを定義できます。また、define-symbol-macroおよびsymbol-macroletを使用して、シンボルのマクロを定義することも可能です。
ポール・グラハムの著書『On Lisp』では、Common Lispにおけるマクロの使用方法が詳細に解説されています。 ダグ・ホイトの著書『Let Over Lambda』では、マクロに関する議論がさらに展開され、「マクロは、プログラミング言語としてのLispの最大の利点であり、あらゆるプログラミング言語の最大の利点でもある」と述べられています。ホイトは、マクロの反復的な開発例をいくつか紹介しています。
マクロを使うと、Lispプログラマーは言語内で新しい構文形式を作成できます。典型的な用途の一つは、新しい制御構造を作成することです。以下のマクロはuntilループ構造を提供します。構文は次のとおりです。
(テストフォームまで*) until のマクロ定義:
( defmacro until ( test &body body ) ( let (( start-tag ( gensym "START" )) ( end-tag ( gensym "END" ))) ` ( tagbody , start-tag ( when , test ( go , end-tag )) ( progn ,@ body ) ( go , start-tag ) , end-tag )))tagbody は、タグに名前を付け、 goフォームを使用してそれらのタグにジャンプする機能を提供する、Common Lisp の基本的な特殊演算子です。バッククォート`は、コード テンプレートを提供する表記法であり、カンマで始まるフォームの値は自動的に入力されます。カンマとアットマークで始まるフォームは、挿入されます。tagbody フォームは終了条件をテストします。条件が真の場合、終了タグにジャンプします。そうでない場合は、指定されたボディ コードが実行され、その後開始タグにジャンプします。
上記のマクロを使用した例:
( until ( = ( random 10 ) 0 ) ( write-line "Hello" ))コードは関数macroexpand-1を使用して展開できます。上記の例の展開は次のようになります。
( TAGBODY #:START1136 ( WHEN ( ZEROP ( RANDOM 10 )) ( GO #:END1137 )) ( PROGN ( WRITE-LINE "hello" )) ( GO #:START1136 ) #:END1137 )マクロ展開中、変数testの値は(= (random 10) 0)であり、変数bodyの値は((write-line "Hello"))です。 body はフォームのリストです。
シンボルは通常自動的に大文字に変換されます。展開では、2 つのラベルを持つ TAGBODY を使用します。これらのラベルのシンボルは GENSYM によって計算され、どのパッケージにもインターンされません。2 つのgoフォームは、これらのタグを使用してジャンプします。tagbody は Common Lisp のプリミティブ演算子 (マクロではない) であるため、他のものに展開されません。展開されたフォームはwhenマクロを使用しますが、これも展開されます。ソース フォームを完全に展開することをコード ウォーキングと呼びます。
完全に展開された(ウォークされた)形式では、when形式はプリミティブifに置き換えられます。
( TAGBODY #:START1136 ( IF ( ZEROP ( RANDOM 10 )) ( PROGN ( GO #:END1137 )) NIL ) ( PROGN ( WRITE-LINE "hello" )) ( GO #:START1136 )) #:END1137 )ソースコードが通常どおり評価またはコンパイルされるには、すべてのマクロを展開する必要があります。マクロは、S式を受け取り、S式を返す関数と考えることができます。これは抽象構文木に似ていますが、それに限定されるものではありません。これらの関数は、評価器またはコンパイラが最終的なソースコードを生成する前に呼び出されます。マクロは通常のCommon Lispで記述され、利用可能な任意のCommon Lisp演算子(またはサードパーティ製演算子)を使用できます。
Common Lispのマクロは、一般的に変数キャプチャと呼ばれる機能を備えています。これは、マクロ展開本体内のシンボルが呼び出しコンテキスト内のシンボルと一致することを意味し、プログラマはさまざまなシンボルに特別な意味を持たせたマクロを作成できます。ただし、 「変数キャプチャ」という用語はやや誤解を招く可能性があります。なぜなら、演算子と関数の名前空間、タグ本体ラベルの名前空間、catchタグ、条件ハンドラ、再起動の名前空間など、すべての名前空間が意図しないキャプチャの影響を受ける可能性があるからです。
変数キャプチャはソフトウェアの欠陥を引き起こす可能性があります。これは、次の2つの方法のいずれかで発生します。
LispのScheme方言は、参照透過性を提供するマクロ記述システムを備えており、これにより両方のタイプのキャプチャ問題が解消されます。この種のマクロシステムは、特にその支持者によって「衛生的」と呼ばれることがあります(彼らは、この問題を自動的に解決しないマクロシステムを非衛生的とみなしています)。
Common Lispでは、マクロの衛生管理は2つの異なる方法のいずれかで保証されます。
一つのアプローチは、 gensyms を使用することです。gensyms は、キャプチャの脅威なしにマクロ展開で使用できる、一意性が保証されたシンボルです。マクロ定義で gensyms を使用するのは手作業ですが、gensyms のインスタンス化と使用を簡素化するマクロを作成できます。gensyms はタイプ 2 のキャプチャを容易に解決しますが、マクロ展開が参照をキャプチャする周囲のコード内の干渉シンボルの名前を変更できないため、タイプ 1 のキャプチャには同じようには適用できません。gensyms は、マクロ展開が必要とするグローバルシンボルに安定したエイリアスを提供するために使用できます。マクロ展開は、よく知られている名前ではなく、これらの秘密のエイリアスを使用するため、よく知られている名前の再定義はマクロに悪影響を与えません。
別の方法として、パッケージを使用する方法があります。独自のパッケージ内で定義されたマクロは、展開時にそのパッケージ内の内部シンボルをそのまま使用できます。パッケージを使用することで、タイプ1およびタイプ2のキャプチャを処理できます。
しかし、パッケージは標準のCommon Lisp関数や演算子への参照のタイプ1キャプチャの問題を解決しません。その理由は、キャプチャの問題を解決するためにパッケージを使用する方法は、プライベートシンボル(あるパッケージ内のシンボルで、他のパッケージにインポートされたり、他のパッケージから見えるようにしたりしないもの)の使用を前提としているからです。一方、Common Lispライブラリのシンボルは外部のものであり、ユーザー定義パッケージにインポートされたり、他のパッケージから見えるようにしたりすることが頻繁にあります。
以下は、マクロの展開時に発生する、演算子名前空間における意図しないキャプチャの例です。
;; UNTIL の展開では DO を多用します( defmacro until ( expression &body body ) ` ( do () ( , expression ) ,@ body ));; macrolet は DO の字句演算子バインディングを確立します( macrolet (( do ( ... ) ... something else ... )) ( until ( = ( random 10 ) 0 ) ( write-line "Hello" )))このマクロは、標準のCommon Lispマクロを参照することを意図したuntil呼び出しを行う形式に展開されます。ただし、このコンテキストでは、は全く異なる意味を持つ可能性があるため、正しく動作しない場合があります。dododountil
Common Lisp は、標準演算子と関数の再定義を禁止することで、それらのシャドウイングの問題を解決します。しかし、標準演算子を再定義しているためdo、前述のコードは実際には準拠していない Common Lisp の断片であり、実装側でそれを診断して拒否することができます。
Common Lisp では、条件システムが例外処理を担当します。[ 18 ]条件、ハンドラ、再起動を提供します。条件は、例外的な状況 (たとえばエラー) を記述するオブジェクトです。条件が通知されると、Common Lisp システムはこの条件タイプのハンドラを検索し、ハンドラを呼び出します。ハンドラは再起動を検索し、条件タイプや条件オブジェクトの一部として提供される関連情報などの情報を使用して、これらの再起動のいずれかを使用して現在の問題を自動的に修復し、適切な再起動関数を呼び出すことができます。
これらの再起動は、コードで処理されない場合、ユーザーに提示され(例えばデバッガなどのユーザーインターフェイスの一部として)、ユーザーは利用可能な再起動のいずれかを選択して実行できます。条件ハンドラはエラーのコンテキストで呼び出されるため(スタックを巻き戻さずに)、他の例外処理システムでは既に現在のルーチンが終了している場合でも、多くの場合、完全なエラー回復が可能です。デバッガ自体も、動的変数を使用してカスタマイズまたは置き換えることができます。ファイナライザなどの巻き戻し保護*debugger-hook*フォーム内のコードも、例外が発生しても適切に実行されます。
次の例(Symbolics Generaを使用)では、Read-Eval-Print-LOOP(REPL )から呼び出されたLisp関数testで、ファイルが存在しない状態でファイルを開こうとします。Lispシステムは4つの再起動オプションを提示します。ユーザーは「別のパス名を使用してOPENを再試行」を選択し、別のパス名(lispm-int.lispではなくlispm-init.lisp)を入力します。ユーザーコードにはエラー処理コードは含まれていません。エラー処理と再起動コードはすべてLispシステムによって提供され、ユーザーコードを終了させることなくエラーを処理および修復できます。
コマンド: (test ">zippy>lispm-int.lisp") エラー:ファイルが見つかりませんでした。 lispmの場合:>zippy>lispm-int.lisp.newest LMFS:OPEN-LOCAL-LMFS-1 引数 0: #P"lispm:>zippy>lispm-int.lisp.newest" sA、<再開>: lispm:>zippy>lispm-int.lisp.newest の OPEN を再試行します sB: 別のパス名を使用してOPENを再試行します sC、<中止>:TELNETサーバーでLispトップレベルに戻る sD: TELNET端末のプロセスを再起動します -> 別のパス名を使用してOPENを再試行してください 代わりにどのパス名を使用しますか [default lispm:>zippy>lispm-int.lisp.newest]: lispm:>zippy>lispm-init.lisp.newest ...プログラムは続きます Common Lisp には、オブジェクト指向プログラミング用のツールキットであるCommon Lisp オブジェクト システム ( CLOS)が含まれています。Peter Norvig は、CLOS の機能 (多重継承、ミックスイン、マルチメソッド、メタクラス、メソッドの組み合わせなど) を備えた動的言語で、多くのデザイン パターンを実装するのがいかに簡単かを説明しています。 [ 19 ] オブジェクト指向プログラミングのための Common Lisp の拡張が ANSI Common Lisp 標準に含めるように提案されましたが、最終的に CLOS が Common Lisp の標準オブジェクト システムとして採用されました。 CLOS は多重ディスパッチと多重継承を備えた動的オブジェクト システムであり、 C++やJavaなどの静的言語に見られる OOP 機能とは根本的に異なります。 動的オブジェクト システムとして、CLOS は実行時に汎用関数とクラスを変更することを可能にします。 メソッドを追加および削除したり、クラスを追加および再定義したり、クラス変更のためにオブジェクトを更新したり、オブジェクトのクラスを変更したりできます。
CLOS は ANSI Common Lisp に統合されています。ジェネリック関数は通常の関数と同様に使用でき、第一級データ型です。すべての CLOS クラスは Common Lisp 型システムに統合されています。多くの Common Lisp 型には対応するクラスがあります。Common Lisp における CLOS の潜在的な用途は他にもあります。仕様では、条件が CLOS で実装されているかどうかは明記されていません。パス名やストリームは CLOS で実装できます。ANSI Common Lisp における CLOS のこれらのさらなる使用可能性は、標準の一部ではありません。実際の Common Lisp 実装では、パス名、ストリーム、入出力、条件、CLOS 自体の実装などに CLOS が使用されています。
Lispインタープリタは、S式から読み取られたLispオブジェクト(リスト、シンボル、数値など)として提供されるLispソースコードを直接実行します。Lispコンパイラは、 Lispソースコードからバイトコードまたはマシンコードを生成します。Common Lispでは、個々のLisp関数をメモリ内でコンパイルすることも、ファイル全体をコンパイルして外部に保存されるコンパイル済みコード(faslファイル)にコンパイルすることもできます。
初期の Lisp 方言のいくつかの実装では、インタプリタとコンパイラの両方が提供されていました。残念ながら、多くの場合、セマンティクスが異なっていました。これらの初期の Lisp では、コンパイラで字句スコープが、インタプリタで動的スコープが実装されていました。Common Lisp では、インタプリタとコンパイラの両方がデフォルトで字句スコープを使用することが求められます。Common Lisp 標準では、インタプリタとコンパイラの両方のセマンティクスが記述されています。コンパイラは、個々の関数に対してはcompile関数を、ファイルに対してはcompile-file関数を使用して呼び出すことができます。Common Lisp では、型宣言が可能で、コンパイラのコード生成ポリシーに影響を与える方法も提供されています。後者については、speed、space、safety、debug 、 compilation-speedなど、さまざまな最適化品質に 0 (重要でない) から 3 (最も重要) までの値を指定できます。
Lisp コードを評価する関数もあります。 は、eval他evalの言語のようにテキスト文字列としてではなく、事前に解析された s 式としてコードを受け取ります。このようにして、リストやシンボルを構築するための通常の Lisp 関数を使用してコードを構築し、その後、このコードを関数で評価できますeval。 Clozure CL や SBCL などの Common Lisp 実装は、evalコンパイラを使用して を実装しています。このようにして、コードは関数を使用して評価されますが、コンパイルされますeval。
ファイルコンパイラは、compile-file 関数を使用して起動します。コンパイルされたコードを含む生成ファイルは、fasl ( fast loadの略) ファイルと呼ばれます。これらのfaslファイルおよびソースコードファイルは、load関数を使用して実行中の Common Lisp システムにロードできます。実装によっては、ファイルコンパイラはバイトコード (例えばJava 仮想マシン用)、C 言語コード (その後 C コンパイラでコンパイルされる)、またはネイティブコードを直接生成します。
Common Lispの実装は、コードが完全にコンパイルされるにもかかわらず、対話的に使用することができます。したがって、対話型Common Lispには、インタプリタ型言語という概念は当てはまりません。
この言語は、読み込み時、コンパイル時、ロード時、実行時を区別し、ユーザーコードもこの区別に基づいて、必要なステップで必要な種類の処理を実行できるようにしています。
対話型開発に特に適した特別な演算子がいくつか用意されています。例えば、 はdefvar指定された変数がまだバインドされていない場合にのみ値を代入しますが、 はdefparameter常に代入を実行します。この違いは、ライブイメージでコードを対話的に評価、コンパイル、ロードする際に役立ちます。
コンパイラやインタプリタの作成を支援する機能もいくつか用意されています。シンボルは第一レベルのオブジェクトで構成され、ユーザーコードから直接操作できます。progv特殊演算子を使用すると、字句バインディングをプログラムで作成でき、パッケージも操作可能です。Lispコンパイラは実行時に利用可能で、ファイルや個々の関数をコンパイルできます。これらの機能により、Lispを他の言語の中間コンパイラまたはインタプリタとして簡単に使用できます。
以下のプログラムは、部屋の中で全員の誕生日が異なる確率が50%未満となる最小人数を計算します(誕生日のパラドックス。1人の場合は明らかに100%、2人の場合は364/365など)。答えは23です。
Common Lispでは、慣例として定数は「+」文字で囲まれます。
( defconstant +year-size+ 365 )( defun birthday-paradox ( probability number-of-people ) ( let (( new-probability ( * ( / ( - +year-size+ number-of-people ) +year-size+ ) probability ))) ( if ( < new-probability 0.5 ) ( 1+ number-of-people ) ( birthday-paradox new-probability ( 1+ number-of-people )))))REPL(Read Eval Print Loop)を使用してサンプル関数を呼び出す:
CL-USER > (birthday-paradox 1.0 1) 23 まず、人の名前と年齢を表示するためのクラスpersonとメソッドを定義します。次に、personオブジェクトのリストとして人物のグループを定義します。そして、ソートされたリストを反復処理します。
( defclass person () (( name :initarg :name :accessor person-name ) ( age :initarg :age :accessor person-age )) ( :documentation "NAME と AGE のスロットを持つクラス PERSON。" ))( defmethod display (( object person ) stream ) "PERSON オブジェクトを出力ストリームに表示します。" ( with-slots ( name age ) object ( format stream "~a (~a)" name age )))( defparameter *group* ( list ( make-instance 'person :name "Bob" :age 33 ) ( make-instance 'person :name "Chris" :age 16 ) ( make-instance 'person :name "Ash" :age 23 )) "PERSON オブジェクトのリスト。" )( dolist ( person ( sort ( copy-list *group* ) #' > :key #' person-age )) ( display person *standard-output* ) ( terpri ))年齢の高い順に3つの名前を表示します。
ボブ(33) アッシュ(23) クリス(16) LOOPマクロの使用例を以下に示します。
( defun power ( x n ) ( loop with result = 1 while ( plusp n ) when ( oddp n ) do ( setf result ( * result x )) do ( setf x ( * x x ) n ( truncate n 2 )) finally ( return result )))使用例:
CL-USER > ( 2 200 のべき乗) 1606938044258990275541962092341162602522202993782792835301376組み込みのべき乗と比較してください。
CL-USER > ( = ( expt 2 200 ) ( power 2 200 )) TWITH-OPEN-FILE は、ファイルを開いてストリームを提供するマクロです。フォームが戻ると、ファイルは自動的に閉じられます。FUNCALL は関数オブジェクトを呼び出します。LOOP は、述語に一致するすべての行を収集します。
( defun list-matching-lines ( file predicate ) "行に適用された述語が T を返す行のリストをファイルから返します。" ( with-open-file ( stream file ) ( loop for line = ( read-line stream nil nil ) while line when ( funcall predicate line ) collect it )))関数 AVAILABLE-SHELLS は、パス名と匿名関数を述語として、上記の関数 LIST-MATCHING-LINES を呼び出します。述語は、シェルのパス名を返すか、NIL (文字列がシェルのファイル名でない場合) を返します。
( defun available-shells ( &optional ( file #p"/etc/shells" )) ( list-matching-lines file ( lambda ( line ) ( and ( plusp ( length line )) ( char= ( char line 0 ) #\/ ) ( pathname ( string-right-trim ' ( #\space #\tab ) line ))))))実行例(Mac OS X 10.6):
CL-USER > (利用可能なシェル) ( #P"/bin/bash" #P"/bin/csh" #P"/bin/ksh" #P"/bin/sh" #P"/bin/tcsh" #P"/bin/zsh" )Common Lispは、最もよく比較され、対比される言語としてSchemeが挙げられる。これは、両者が最も人気のあるLisp方言だからに他ならない。SchemeはCommon Lispよりも歴史が古く、同じLispの伝統を受け継いでいるだけでなく、同じエンジニアたちによって開発された。例えば、 Gerald Jay Sussmanと共にSchemeを設計したGuy Steeleは、Common Lispの標準化委員会の委員長を務めた。
Common Lispは汎用プログラミング言語であり、Emacs LispやAutoLISPといったLispの派生言語(それぞれGNU EmacsとAutoCADに組み込まれた拡張言語)とは対照的です。多くの初期のLispとは異なり、Common Lispは( Schemeと同様に)デフォルトで、インタプリタ型コードとコンパイル型コードの両方でレキシカル変数スコープを使用します。
Common Lisp の設計に貢献した Lisp システム( ZetaLispや Franz Lispなど)のほとんどは、インタプリタでは動的スコープの変数を、コンパイラでは字句スコープの変数を使用していた。Scheme は、 ALGOL 68に触発され、Lisp に字句スコープの変数のみを使用する方式を導入した。CL は動的スコープの変数もサポートしているが、明示的に「special」として宣言する必要がある。ANSI CL のインタプリタとコンパイラの間には、スコープに関する違いはない。
Common Lisp はLisp-2、Scheme はLisp-1と呼ばれることがありますが、これは CL が関数と変数に別々の名前空間を使用していることに由来します。(実際には、CL には go タグ、ブロック名、キーワードなど、多くのloop名前空間があります。)複数の名前空間に伴うトレードオフについては、CL 支持者と Scheme 支持者の間で長年にわたる論争があります。Scheme では、(大まかに)関数と衝突する変数名を付けないようにする必要があります。Scheme 関数は、システム関数と衝突しないようにlis、引数にlst、 、 などの名前がよく付けられます。しかし、CL では、関数を引数として渡すときに関数の名前空間を明示的に参照する必要があります。これは、上記の例のようによくあることです。lystlistsort
CL はブール値の扱い方においても Scheme と異なります。Scheme では、真偽を表すのに特殊値 #t と #f を使用します。CL は、古い Lisp の慣習に従い、シンボル T と NIL を使用します。NIL は空リストも表します。CL では、などの条件式で NIL 以外の値はすべて真として扱われますが、Schemeifでは #f 以外の値はすべて真として扱われます。これらの慣習により、両言語の一部の演算子は、述語 (ブール値の質問に答える) としても、さらなる計算に役立つ値を返すものとしても機能しますが、Scheme では Common Lisp の NIL に相当する値 '() は、ブール式では真と評価されます。
最後に、Scheme の標準文書では末尾呼び出し最適化が必須とされていますが、CL の標準では必須ではありません。ほとんどの CL 実装では末尾呼び出し最適化が提供されていますが、多くの場合、プログラマが最適化ディレクティブを使用した場合に限られます。とはいえ、一般的な CL のコーディングスタイルでは、Scheme スタイルが好むような再帰の遍在的な使用は推奨されていません。Scheme プログラマが末尾再帰で表現するものを、CL ユーザーは通常、、、、、または (最近では) パッケージの反復式で表現doします。dolistloopiterate
「Common Lisp実装」カテゴリを参照してください。
Common Lispは、 Perlのように単一の実装によって定義されるのではなく、AdaやCのように仕様によって定義されます。多くの実装が存在し、標準規格ではそれらが正当に異なることが許容される領域が詳細に規定されています。
さらに、実装には標準規格でカバーされていない機能を提供する拡張機能が付属する傾向がある。
移植可能な方法でCommon Lispの拡張をサポートするために、フリーでオープンソースのソフトウェアライブラリが作成されており、特にCommon-Lisp.net [ 20 ]およびCLOCC(Common Lisp Open Code Collection)[ 21 ]プロジェクトのリポジトリで見つけることができます。
Common Lisp の実装では、ネイティブ コード コンパイル、バイト コード コンパイル、またはインタプリタ方式を自由に組み合わせて使用できます。Common Lisp は、インクリメンタル コンパイラ、ファイル コンパイラ、およびブロック コンパイラをサポートするように設計されています。コンパイルを最適化するための標準宣言 (関数のインライン化や型の特殊化など) が言語仕様で提案されています。ほとんどの Common Lisp 実装は、ソース コードをネイティブマシン コードにコンパイルします。一部の実装では、(最適化された) スタンドアロン アプリケーションを作成できます。他の実装では、ネイティブ コードよりも効率は劣りますが、バイナリ コードの移植性を容易にするインタプリタバイト コードにコンパイルします。一部のコンパイラは、Common Lisp コードを C コードにコンパイルします。Lisp が純粋にインタプリタ型の言語であるという誤解は、Lisp 環境が対話型のプロンプトを提供し、コードがインクリメンタル方式で 1 つずつコンパイルされることに起因していると考えられます。Common Lisp では、インクリメンタル コンパイルが広く使用されています。
Unixベースの実装(CLISP、SBCL )の中には、スクリプト言語として使用できるものがあります。つまり、 PerlやUnixシェルインタープリタのように、システムから透過的に呼び出されます。 [ 22 ]
Common Lispは、研究用アプリケーション(特に人工知能分野)の開発、プロトタイプの迅速な開発、または実用化されたアプリケーションの開発に使用されます。
Common Lispは、多くの商用アプリケーションで使用されており、その中には、当初ポール・グラハムが関わっていたものの、後にC++とPerlで書き直されたYahoo!ストアのウェブコマースサイトも含まれる。[ 47 ]その他の注目すべき例としては、以下が挙げられる。
Common Lispで書かれたオープンソースアプリケーションも存在します。例えば、以下のようなものがあります。
{{cite journal}}:ジャーナルを引用するには|journal=(ヘルプ){{cite journal}}:ジャーナルを引用するには|journal=(ヘルプ)Common Lisp(言語)またはCommon Lispを用いたプログラミング(特にAIプログラミング)について出版された(または出版予定の)書籍を時系列順にリスト化したもの。