Common LISPを使用すると、必要なソース変換を行うマクロを作成できます。
Schemeは、変換も実行できる衛生的なパターンマッチングシステムを提供します。マクロは実際にどの程度役に立ちますか?ポール・グラハムは 平均を打ち負かす でこう言った:
Viawebエディターのソースコードは、おそらく約20-25%のマクロでした。
人々は実際にマクロでどのようなことをするのでしょうか?
2002年のLL1ディスカッションリストへのMatthias Felleisenによる この投稿 をご覧ください。マクロの3つの主な使用法を提案しています。
- Data sublanguages:シンプルに見える式を記述し、複雑な入れ子のリスト/配列/テーブルを引用符、引用符などで作成できます。
- バインディングコンストラクト:マクロを使用して新しいバインディングコンストラクトを導入できます。それは私がラムダを取り除き、一緒に属しているものを一緒に近くに置くのを助けます。
- 評価の並べ替え:必要に応じて、式の評価を遅延/延期する構成を導入できます。ループ、新しい条件、遅延/強制などについて考えてください。[注:Haskellまたはその他の遅延言語では、これは不要です。]
主にマクロを使用して、時間を節約する新しい言語構造を追加します。そうしないと、ボイラープレートコードの束が必要になります。
たとえば、私は最近、必須のfor-loop
C++/Javaに似ています。ただし、関数型言語であるClojureには、箱から出してすぐに使用できるものはありませんでした。だから私はそれをマクロとして実装しました:
(defmacro for-loop [[sym init check change :as params] & steps]
`(loop [~sym ~init value# nil]
(if ~check
(let [new-value# (do ~@steps)]
(recur ~change new-value#))
value#)))
そして今私はできる:
(for-loop [i 0 , (< i 10) , (inc i)]
(println i))
そして、あなたはそれを持っています-6行のコードでの新しい汎用コンパイル時言語構成体。
ここではいくつかの例を示します。
スキーム:
define
。基本的には、関数を定義するためのより短い方法になります。let
は、レキシカルスコープの変数を作成します。Clojure:
defn
、そのドキュメントによると:
(def name(fn [params *] exprs *))または(def name(fn([params *] exprs *)+))と同じですが、任意のdoc-stringまたはattrsがvarメタデータに追加されます
for
:内包表記のリストdefmacro
:皮肉?defmethod
、defmulti
:マルチメソッドでの作業ns
これらのマクロの多くは、より抽象的なレベルでコードを書くことをはるかに簡単にします。マクロは、多くの点で、非Lispの構文に似ていると思います。
プロットライブラリ Incanter は、いくつかの複雑なデータ操作のためのマクロを提供します。
マクロはいくつかのパターンを埋め込むのに役立ちます。
たとえば、Common LISPはwhile
ループを定義していませんが、それを定義するために使用できるdo
を持っています。
On LISP の例を次に示します。
(defmacro while (test &body body)
`(do ()
((not ,test))
,@body))
(let ((a 0))
(while (< a 10)
(princ (incf a))))
これは「12345678910」を出力し、macroexpand-1
で何が起こるかを確認しようとすると、次のようになります。
(macroexpand-1 '(while (< a 10) (princ (incf a))))
これは戻ります:
(DO () ((NOT (< A 10))) (PRINC (INCF A)))
これは単純なマクロですが、前述のように、通常は新しい言語またはDSLを定義するために使用されますが、この単純な例から、それらで何ができるかを想像することができます。
loop
マクロは、マクロが実行できることの良い例です。
(loop for i from 0 to 10
if (and (= (mod i 2) 0) i)
collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
with a = 2
collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)
一般的なLISPには、readerマクロと呼ばれる別の種類のマクロがあります。これを使用して、リーダーがコードを解釈する方法を変更できます。つまり、マクロを使用して#{そして#}には#(と#)のような区切り文字があります。
これが私がデバッグに使用するものです(Clojureで):
_user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil
_
get
メソッドが非const文字列参照を引数として取り、C++で手作業のハッシュテーブルを処理する必要がありました。つまり、リテラルで呼び出すことはできません。それを扱いやすくするために、私は次のようなものを書きました:
_#define LET(name, value, body) \
do { \
string name(value); \
body; \
assert(name == value); \
} while (false)
_
LISPでこのような問題が発生する可能性は低いですが、たとえばreal let-bindingを導入することで、引数を2回評価しないマクロを使用できることが特にいいと思います。 (認めた、ここで私はそれを回避できたかもしれない)。
また、do ... while (false)
でものをラップするというひどく醜いハックに頼って、ifのthen部分でそれを使用しても、期待どおりにelse部分の動作を維持できるようにします。これは、LISPでは必要ありません。LISPは、構文解析を行う文字列(またはCとC++の場合はトークンシーケンス)ではなく、構文ツリーで動作するマクロの関数です。
いくつかの組み込みスレッディングマクロがあり、コードを再構成してよりきれいに読み取ることができます(並列処理ではなく、「コードを一緒にまく」のような「スレッディング」)。例えば:
_(->> (range 6) (filter even?) (map inc) (reduce *))
_
これは最初の形式_(range 6)
_を取り、次の形式の最後の引数_(filter even?)
_にします。これは、次の形式の最後の引数になり、以下同様です。に書き直された
_(reduce * (map inc (filter even? (range 6))))
_
私は最初の方がはるかに明確に読んだと思います:「これらのデータを取得し、それに実行し、次にそれを実行し、次に他のことを実行すれば完了です」が、それは主観的です。客観的に当てはまるのは、操作が実行される順序で操作を読み取ることです(遅延を無視)。
前のフォームを(最後ではなく)最初の引数として挿入するバリアントもあります。 1つの使用例は算術です。
_(-> 17 (- 2) (/ 3))
_
「テイク17、2を引き、3で除算する」と読みます。
算術と言えば、インフィックス表記法の構文解析を行うマクロを書くことができます。 _(infix (17 - 2) / 3)
_そしてそれは_(/ (- 17 2) 3)
_を吐き出しますが、これは読みにくくなるという欠点と有効なLISP式であるという利点があります。それがDSL /データのサブ言語部分です。