多くのLispersは、どのようなLispのスペシャルを作ることであることを教えてくれますhomoiconicityコードの構文は、他のデータと同じデータ構造を用いて表現されることを意味し、。たとえば、次は、指定された辺の長さの直角三角形の斜辺を計算するための単純な関数です(Scheme構文を使用)。
(define (hypot x y)
(sqrt (+ (square x) (square y))))
さて、ホモイコニシティは、上記のコードは実際にはLispコードのデータ構造(具体的にはリストのリスト)として表現可能であると言っています。したがって、次のリストを検討し、それらがどのように「接着」するかを確認してください。
(define #2# #3#)
(hypot x y)
(sqrt #4#)
(+ #5# #6#)
(square x)
(square y)
マクロを使用すると、ソースコードを単なるもののリストとして扱うことができます。これら6「サブリスト」はそれぞれ、他のリストのいずれかのポインタが含まれている、または記号に(この例では:define
、hypot
、x
、y
、sqrt
、+
、square
)。
では、どのようにホモイコニシティを使用して構文を「選択」し、マクロを作成できますか?以下に簡単な例を示します。let
を呼び出すマクロを再実装しましょうmy-let
。念のため、
(my-let ((foo 1)
(bar 2))
(+ foo bar))
に展開する必要があります
((lambda (foo bar)
(+ foo bar))
1 2)
以下に、Schemeの「明示的な名前変更」マクロを使用した実装を示します†:
(define-syntax my-let
(er-macro-transformer
(lambda (form rename compare)
(define bindings (cadr form))
(define body (cddr form))
`((,(rename 'lambda) ,(map car bindings)
,@body)
,@(map cadr bindings)))))
form
パラメータは、実際のフォームにバインドされているので、この例のために、それは次のようになります(my-let ((foo 1) (bar 2)) (+ foo bar))
。それでは、例を見ていきましょう。
- まず、フォームからバインディングを取得します。フォーム
cadr
の((foo 1) (bar 2))
一部を取得します。
次に、フォームから本文を取得します。フォームcddr
の((+ foo bar))
一部を取得します。(これは、バインディング後のすべてのサブフォームを取得することを目的としていることに注意してください。
(my-let ((foo 1)
(bar 2))
(debug foo)
(debug bar)
(+ foo bar))
その後、体はなります((debug foo) (debug bar) (+ foo bar))
。)
- ここで、結果の
lambda
式を実際に構築し、収集したバインディングと本体を使用して呼び出します。バックティックは「準クォート」と呼ばれます。これは、コンマの後のビットを除く、準クォート内のすべてをリテラルデータムとして扱うことを意味します(「アンクォート」)。
(rename 'lambda)
手段が使用するlambda
このマクロがされたときに力で結合を定義し、むしろ何よりもlambda
このマクロがされたときに周りのかもしれませんバインディングを使用します。(これは衛生として知られています。)
(map car bindings)
戻り値(foo bar)
:各バインディングの最初のデータ。
(map cadr bindings)
戻り値(1 2)
:各バインディングの2番目のデータ。
,@
リストを返す式に使用される「スプライシング」を行います。リスト自体ではなく、リストの要素を結果に貼り付けます。
- これらすべてをまとめると、結果としてlist
(($lambda (foo bar) (+ foo bar)) 1 2)
が得られ$lambda
ますlambda
。ここで、renamedを指します。
簡単ですよね?;-)(あなたにとって簡単でない場合は、他の言語のマクロシステムを実装することがどれほど難しいか想像してください。)
したがって、ソースコードを不格好な方法で「分離」できる方法があれば、他の言語のマクロシステムを使用できます。これにはいくつかの試みがあります。たとえば、sweet.jsはJavaScriptに対してこれを行います。
†これを読んでいる熟練したSchemer defmacro
のために、他のLisp方言で使用されるsの間の妥協案として明示的な名前変更マクロを使用することを意図的に選択しましたsyntax-rules
。私は他のLisp方言で書きたくありませんが、慣れていない非Schemersを疎外したくありませんsyntax-rules
。
参考のために、次のmy-let
マクロを使用しますsyntax-rules
:
(define-syntax my-let
(syntax-rules ()
((my-let ((id val) ...)
body ...)
((lambda (id ...)
body ...)
val ...))))
対応するsyntax-case
バージョンは非常に似ています:
(define-syntax my-let
(lambda (stx)
(syntax-case stx ()
((_ ((id val) ...)
body ...)
#'((lambda (id ...)
body ...)
val ...)))))
2つの違いは、のすべてにsyntax-rules
暗黙的な#'
適用があるため、でパターンとテンプレートのペアのみを持つことができるためsyntax-rules
、完全に宣言的であるということです。対照的に、syntax-case
では、パターンの後のビットは実際にはコードであり、最終的には構文オブジェクト(#'(...)
)を返す必要がありますが、他のコードも含めることができます。