Common Lispでは、必要なソース変換を行うマクロを作成できます。
Schemeは、変換も実行できる衛生的なパターンマッチングシステムを提供します。マクロは実際にどの程度役立ちますか?ポール・グラハムは、ビーティング・ザ・アベレージズで次のように述べています:
Viawebエディターのソースコードは、おそらく20〜25%のマクロでした。
実際に人々はどのようなことをマクロでやっていますか?
Common Lispでは、必要なソース変換を行うマクロを作成できます。
Schemeは、変換も実行できる衛生的なパターンマッチングシステムを提供します。マクロは実際にどの程度役立ちますか?ポール・グラハムは、ビーティング・ザ・アベレージズで次のように述べています:
Viawebエディターのソースコードは、おそらく20〜25%のマクロでした。
実際に人々はどのようなことをマクロでやっていますか?
回答:
2002年のLL1ディスカッションリストへのMatthias Felleisenによるこの投稿を見てください。彼はマクロの3つの主な用途を提案しています。
- データのサブ言語:シンプルに見える式を作成し、マクロをきちんとドレスアップして、引用、引用解除などの複雑なネストされたリスト/配列/テーブルを作成できます。
- バインディングコンストラクト:マクロを使用して新しいバインディングコンストラクトを導入できます。それは、ラムダを取り除き、一緒に属するものをより近くに配置するのに役立ちます。
- 評価の並べ替え:必要に応じて式の評価を遅延/延期する構成を導入できます。ループ、新しい条件、遅延/強制などを考えてください。[注意: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
、そのドキュメントによると:
var-metadataにdoc-stringまたはattrsが追加された(def name(fn [params *] exprs *))または(def name(fn([params *] exprs *)+))と同じ
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)
Common Lispには、リーダーマクロと呼ばれる別の種類のマクロがあり、リーダーがコードを解釈する方法を変更するために使用できます。つまり、#{および#}を使用して#(および#)のような区切り文字を使用できます
以下は、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で発生することはまずありませんが、たとえば実際の letバインディングを導入することで、引数を2回評価しないマクロを使用できることは特に便利です。(認めた、ここで私はそれを回避できたかもしれない)。
またdo ... while (false)
、ifのthen-partで使用し、else-partが期待どおりに動作するように、ものをラッピングするというひどくいハックに頼っています。これは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 /データサブ言語部分です。