プログラムでマクロを使用する必要があるのはいつですか?
この質問は、@ tarsiusによる有益な回答に触発されたものです。多くの人が他の人と共有する素晴らしい経験があると思います。この質問がすばらしい主観的な質問の 1つになり、Emacsプログラマーの助けになることを願っています。
プログラムでマクロを使用する必要があるのはいつですか?
この質問は、@ tarsiusによる有益な回答に触発されたものです。多くの人が他の人と共有する素晴らしい経験があると思います。この質問がすばらしい主観的な質問の 1つになり、Emacsプログラマーの助けになることを願っています。
回答:
構文にはマクロを使用しますが、セマンティクスには使用しません。
構文糖にマクロを使用することは問題ありませんが、マクロ自体にあるか展開にあるかに関係なく、マクロ本体にロジックを配置することは悪い考えです。そうする必要があると感じたら、代わりに関数を書き、構文糖の「コンパニオンマクロ」を書きます。簡単に言えば、let
マクロに問題がある場合は、トラブルを探しています。
マクロには非常に多くの欠点があります。
私や他の人のElispコードを読んでデバッグし、修正した経験から、マクロをできるだけ避けることをお勧めします。
マクロに関する明らかなことは、引数を評価しないことです。つまり、マクロでラップされた複雑な式がある場合、マクロ定義を検索しない限り、評価されるかどうか、またどのコンテキストで評価されるかはわかりません。あなたは自分のマクロの定義を知っているので、これはあなたにとって大したことではないかもしれません(現在、おそらく数年後ではないでしょう)。
この欠点は関数には当てはまりません。すべての引数は関数呼び出しの前に評価されます。これは、未知のデバッグ状況で評価の複雑さを構築できることを意味します。単純なデータを返す限り、未知の関数の定義をまっすぐスキップすることさえでき、それらはグローバルな状態に触れないと仮定します。
別の言い方をすれば、マクロは言語の一部になります。未知のマクロを含むコードを読んでいる場合、あなたはほとんど知らない言語のコードを読んでいます。
以下のような標準マクロはpush
、pop
、dolist
あなたのために本当に多くのことを行うと、他のプログラマによく知られています。これらは基本的に言語仕様の一部です。しかし、独自のマクロを標準化することは事実上不可能です。上記の例のようにシンプルで便利なものを思い付くのが難しいだけでなく、すべてのプログラマーにそれを学ぶよう説得することはさらに難しいです。
また、マクロを気にしません。マクロはsave-selected-window
状態を保存および復元し、同じ方法で引数を評価progn
します。かなり簡単で、実際にはコードが簡単になります。
OKマクロの別の例は、defhydra
またはのようなものuse-package
です。これらのマクロには基本的に独自のミニ言語が付属しています。そして、彼らはまだ単純なデータを扱うか、のように振る舞いprogn
ます。加えて、それらは通常トップレベルにあるため、マクロ引数が依存する呼び出しスタックには変数がないため、少し簡単になります。
悪いマクロの例は、私の意見では、あるmagit-section-action
とmagit-section-case
Magitインチ 最初のものはすでに削除されていますが、2番目のものはまだ残っています。
率直に言って、「セマンティクスではなく構文に使用する」という意味がわかりません。これが、この答えの一部がlunaryonの答えを扱う理由であり、他の部分は元の質問に答える私の試みです。
「セマンティクス」という言葉がプログラミングで使用されるとき、それは言語の作者が彼らの言語を解釈することを選んだ方法を指します。これは通常、言語の文法規則を読者がよく知っていると思われる他の規則に変換する一連の式に要約されます。たとえば、Plotkinは著書「Operational Semanticsへの構造的アプローチ」で、論理派生言語を使用して、抽象構文の評価対象(プログラムの実行とはどういう意味ですか)を説明しています。言い換えれば、構文が何らかのレベルでプログラミング言語を構成するものである場合、何らかのセマンティクスを持たなければなりません。そうでなければ、プログラミング言語ではありません。
しかし、当面は正式な定義を忘れましょう。結局のところ、意図を理解することが重要です。したがって、lunaryonは、プログラムに新しい意味を追加するのではなく、ある種の略語を追加する場合に、読者にマクロを使用するように勧めているようです。私にはこれは奇妙に聞こえ、マクロが実際に使用される方法とはまったく対照的です。以下に、新しい意味を明確に作成する例を示します。
defun
言語の重要な部分である友人は、関数呼び出しを記述するのと同じ用語で記述することはできません。
setf
関数がどのように機能するかを記述する規則は、のような式の効果を記述するには不十分(setf (aref x y) z)
です。
with-output-to-string
また、マクロ内のコードのセマンティクスを変更します。同様に、with-current-buffer
および他のwith-
マクロの束。
dotimes
また、同様の機能を関数の観点から説明することはできません。
ignore-errors
ラップするコードのセマンティクスを変更します。
Emacs Lispで使用されているcl-lib
パッケージ、eieio
パッケージ、およびその他のほぼどこにでもあるライブラリには、マクロが多数定義されていますが、関数フォームは異なる解釈をしているように見えます。
そのため、マクロは、新しいセマンティクスを言語に導入するためのツールです。それを行う別の方法を考えることはできません(少なくともEmacs Lispでは)。
マクロを使用しない場合:
新しいセマンティクスを使用して新しいコンストラクトを導入しない場合(つまり、関数が同じように機能します)。
これが単なる利便性の場合(つまり、短くするためにmvb
展開される名前のマクロを作成するcl-multiple-value-bind
)。
多くのエラー処理コードがマクロによって隠されると予想される場合(既に述べたように、マクロのデバッグは困難です)。
関数よりもマクロを強く好む場合:
ドメイン固有の概念が関数呼び出しによって隠されている場合。たとえばcontext
、連続して呼び出す必要がある一連の関数に同じ引数を渡す必要がある場合、context
引数が隠されているマクロでそれらをラップすることをお勧めします。
関数形式の汎用コードが過度に冗長である場合(Emacs Lispのむき出しの繰り返し構造は私の好みには冗長すぎるため、プログラマを不必要に低レベルの実装の詳細にさらすことになります)。
以下は、コンピューターサイエンスのセマンティクスが何を意味しているのかを直感的に理解できるように意図された、骨抜きバージョンです。
これらの構文規則だけを備えた非常に単純なプログラミング言語があるとします。
variable := 1 | 0
operation := +
expression := variable | (expression operation expression)
この言語を使用すると、(1+0)+1
or 0
または((0+0))
etcなどの「プログラム」を構築できます。
しかし、セマンティックルールを提供しない限り、これらの「プログラム」は無意味です。
今、言語に(セマンティック)ルールを装備しているとします:
0+0 0+1 1+0 1+1 (exp)
---, ---, ---, ---, -----
0 1+0 1 0 exp
これで、この言語を使用して実際に計算できます。つまり、構文表現を取得して抽象的に理解し(言い換えれば、抽象構文に変換)、セマンティックルールを使用してこの表現を操作できます。
Lispファミリーの言語について話すとき、関数評価の基本的なセマンティックルールは、おおよそラムダ計算のルールです。
(f x) (lambda x y) x
-----, ------------, ---
fx ^x.y x
引用およびマクロ拡張メカニズムは、メタプログラミングツールとしても知られています。つまり、プログラミングだけでなく、プログラムについて話すことができます。「メタ層」を使用して新しいセマンティクスを作成することにより、これを実現します。このような単純なマクロの例は次のとおりです。
(a . b)
-------
(cons a b)
これは実際にはEmacs Lispのマクロではありませんが、そうであったかもしれません。私は単純化のためだけに選んだ。最も近い候補は関数であると(f x)
解釈さf
れますが、a
必ずしも関数であるとは限らないため、上記のセマンティックルールはいずれもこのケースに適用されないことに注意してください。
(x y)
はおおよそ「yを評価してから、前の評価の結果でxを呼び出す」ことです。を記述すると(defun x y)
、yは評価されず、xも評価されません。異なるセマンティクスを使用して同等の効果を持つコードを作成できるかどうかは、独自のセマンティクスを持つこのコードを否定しません。
(defun x y)
関数アプリケーションを解釈するためのルールです。