ご存知かもしれませんが、Lisp 複合フォームは通常、外側から内側に処理されます。フォームを理解するには、最も外側のネストの最初の位置にあるシンボルを見なければなりません。その記号は、形の意味を完全に決定します。次の表現はすべて(b c)
、まったく異なる意味を含んでいます。(b c)
したがって、最初に部品を分析することによってそれらを理解することはできません。
;; Common Lisp: define a class A derived from B and C
(defclass a (b c) ())
;; Common Lisp: define a function of two arguments
(defun a (b c) ())
;; add A to the result of calling function B on variable C:
(+ a (b c))
伝統的に、Lisp 方言はフォームを演算子フォームと関数呼び出しフォームに分けてきました。演算子形式は完全に恣意的な意味を持ち、その関数をコンパイルまたは解釈するコードによって決定されます (たとえば、評価は関数呼び出しのすべての引数形式を単純に再帰し、結果の値が関数に渡されます)。
初期の歴史から、Lisp はユーザーが独自の演算子を書くことを許可してきました。fexprs
これには、解釈演算子 (歴史的には として知られている) と、マクロとして知られるコンパイル演算子の 2 つのアプローチがありました。どちらも、評価されていない形式を引数として受け取る関数のアイデアに依存しているため、カスタム戦略を実装できるため、新しい動作で評価モデルを拡張できます。
型fexpr
演算子は、変数の値などを検索できる環境オブジェクトとともに、実行時にフォームを渡すだけです。次に、そのオペレーターがフォームをウォークし、動作を実装します。
マクロ演算子は、マクロ展開時にフォームを渡されます (これは通常、トップレベルのフォームが評価またはコンパイルされる直前に読み込まれるときに発生します)。その仕事は、フォームの動作を解釈することではなく、コードを生成して変換することです。つまり、マクロはミニコンパイラです。(生成されたコードには、さらに多くのマクロ呼び出しを含めることができます。マクロ エキスパンダーがそれを処理し、すべてのマクロ呼び出しがデシメートされるようにします。)
このfexpr
アプローチは、おそらく非効率的であるため、支持されなくなりました。それは基本的にコンパイルを不可能にしますが、Lisp ハッカーはコンパイルを重視しました。(Lisp は 1960 年頃には既にコンパイル済み言語でした。) このfexpr
アプローチは字句環境に対しても敵対的です。fexpr
関数である が、呼び出されたフォームの変数バインディング環境をピアリングできる必要があります。これは、レキシカルスコープでは許可されていない一種のカプセル化違反です。
マクロの記述は fexprs よりも少し難しく、ある意味では柔軟性に欠けますが、1960 年代から 70 年代にかけて、Lisp ではマクロ記述のサポートが改善され、できる限り簡単に近づけるようになりました。マクロはもともとフォーム全体を受け取り、それを自分で解析する必要がありました。マクロ定義システムは、構文のネストされた側面を含む、分解された構文を簡単に消化できる部分で受け取る引数をマクロ関数に提供するものに発展しました。コード テンプレートを記述するためのバッククォート構文も開発され、コード生成の表現がはるかに簡単になりました。
あなたの質問に答えるために、どうすればそのようなフォームを自分で書くことができますか? たとえば、次の場合:
;; Imitation of old-fashioned technique: receive the whole form,
;; extract parts from it and return the translation.
;; Common Lisp defmacro supports this via the &whole keyword
;; in macro lambda lists which lets us have access to the whole form.
;;
;; (Because we are using defmacro, we need to declare arguments "an co &optional al",
;; to make this a three argument macro with an optional third argument, but
;; we don't use those arguments. In ancient lisps, they would not appear:
;; a macro would be a one-argument function, and would have to check the number
;; of arguments itself, to flag bad syntax like (my-if 42) or (my-if).)
;;
(defmacro my-if (&whole if-form an co &optional al)
(let ((antecedent (second if-form)) ;; extract pieces ourselves
(consequent (third if-form)) ;; from whole (my-if ...) form
(alternative (fourth if-form)))
(list 'cond (list antecedent consequent) (list t alternative))))
;; "Modern" version. Use the parsed arguments, and also take advantage of
;; backquote syntax to write the COND with a syntax that looks like the code.
(defmacro my-if (antecedent consequent &optional alternative)
`(cond (,antecedent ,consequent) (t ,alternative))))
もともと Lisp には しかなかったので、これは適切な例ですcond
。if
McCarthy's Lispにはありませんでした。cond
その「シンタックス シュガー」は、おそらく上記のように に展開されるマクロとして後で発明されましたmy-if
。