マクロを使用する場合と使用しない場合[クローズ]


12

プログラムでマクロを使用する必要があるのはいつですか?

この質問は、@ tarsiusによる有益な回答に触発されものです。多くの人が他の人と共有する素晴らしい経験があると思います。この質問がすばらしい主観的な質問の 1つになり、Emacsプログラマーの助けになることを願っています。

回答:


13

経験則

構文にはマクロを使用しますが、セマンティクスに使用しません

構文糖にマクロを使用することは問題ありませんが、マクロ自体にあるか展開にあるかに関係なく、マクロ本体にロジックを配置することは悪い考えです。そうする必要があると感じたら、代わりに関数を書き、構文糖の「コンパニオンマクロ」を書きます。簡単に言えば、letマクロに問題がある場合は、トラブルを探しています。

どうして?

マクロには非常に多くの欠点があります。

  • これらはコンパイル時に拡張されるため、複数のバージョン間でバイナリ互換性を維持するために慎重に記述する必要があります。たとえば、マクロの展開で使用される関数または変数を削除すると、このマクロを使用していたすべての依存パッケージのバイトコードが破損します。言い換えると、互換性のない方法でマクロの実装を変更した場合、すべての依存パッケージを再コンパイルする必要があります。また、バイナリ互換性は必ずしも明らかではありません…
  • 直感的な評価順序を維持するように注意する必要があります。フォームを頻繁に、または間違ったタイミングで、または間違った場所で評価するマクロを書くのは簡単です。
  • バインディングセマンティクスに注意する必要があります。マクロは拡張サイトからバインディングコンテキストを継承します。つまり、どのレキシカル変数が存在するのか、レキシカルバインディングが利用可能かどうかを事前に知りません。マクロは両方のバインディングセマンティクスで動作する必要があります。
  • これに関連して、クロージャー作成するマクロを書くときは注意する必要があります。覚えておいて、字句の定義からではなく、拡張サイトからバインドを取得するので、何を閉じるか、クロージャーをどのように作成するかを注意してください(拡張サイトで字句バインディングがアクティブかどうか、クロージャも利用できます)。

1
字句バインディングとマクロ展開に関する注意事項は特に興味深いものです。ありがとう、lunaryorn。(私が書いたどのコードに対しても字句バインディングを有効にしたことはないので、これまで考えたことのある競合ではありません。)
phils

6

私や他の人のElispコードを読んでデバッグし、修正した経験から、マクロをできるだけ避けることをお勧めします。

マクロに関する明らかなことは、引数を評価しないことです。つまり、マクロでラップされた複雑な式がある場合、マクロ定義を検索しない限り、評価されるかどうか、またどのコンテキストで評価されるかはわかりません。あなたは自分のマクロの定義を知っているので、これはあなたにとって大したことではないかもしれません(現在、おそらく数年後ではないでしょう)。

この欠点は関数には当てはまりません。すべての引数は関数呼び出しの前に評価されます。これは、未知のデバッグ状況で評価の複雑さを構築できることを意味します。単純なデータを返す限り、未知の関数の定義をまっすぐスキップすることさえでき、それらはグローバルな状態に触れないと仮定します。

別の言い方をすれば、マクロは言語の一部になります。未知のマクロを含むコードを読んでいる場合、あなたはほとんど知らない言語のコードを読んでいます。

上記の予約の例外:

以下のような標準マクロはpushpopdolistあなたのために本当に多くのことを行うと、他のプログラマによく知られています。これらは基本的に言語仕様の一部です。しかし、独自のマクロを標準化することは事実上不可能です。上記の例のようにシンプルで便利なものを思い付くのが難しいだけでなく、すべてのプログラマーにそれを学ぶよう説得することはさらに難しいです。

また、マクロを気にしません。マクロはsave-selected-window状態を保存および復元し、同じ方法で引数を評価prognします。かなり簡単で、実際にはコードが簡単になります。

OKマクロの別の例は、defhydraまたはのようなものuse-packageです。これらのマクロには基本的に独自のミニ言語が付属しています。そして、彼らはまだ単純なデータを扱うか、のように振る舞いprognます。加えて、それらは通常トップレベルにあるため、マクロ引数が依存する呼び出しスタックには変数がないため、少し簡単になります。

悪いマクロの例は、私の意見では、あるmagit-section-actionmagit-section-caseMagitインチ 最初のものはすでに削除されていますが、2番目のものはまだ残っています。


4

率直に言って、「セマンティクスではなく構文に使用する」という意味がわかりません。これが、この答えの一部がlunaryonの答えを扱う理由であり、他の部分は元の質問に答える私の試みです。

「セマンティクス」という言葉がプログラミングで使用されるとき、それは言語の作者が彼らの言語を解釈することを選んだ方法を指します。これは通常、言語の文法規則を読者がよく知っていると思われる他の規則に変換する一連の式に要約されます。たとえば、Plotkinは著書「Operational Semanticsへの構造的アプローチ」で、論理派生言語を使用して、抽象構文の評価対象(プログラムの実行とはどういう意味ですか)を説明しています。言い換えれば、構文が何らかのレベルでプログラミング言語を構成するものである場合、何らかのセマンティクスを持たなければなりません。そうでなければ、プログラミング言語ではありません。

しかし、当面は正式な定義を忘れましょう。結局のところ、意図を理解することが重要です。したがって、lunaryonは、プログラムに新しい意味を追加するのではなく、ある種の略語を追加する場合に、読者にマクロを使用するように勧めているようです。私にはこれは奇妙に聞こえ、マクロが実際に使用される方法とはまったく対照的です。以下に、新しい意味を明確に作成する例を示します。

  1. defun 言語の重要な部分である友人は、関数呼び出しを記述するのと同じ用語で記述することはできません。

  2. setf関数がどのように機能するかを記述する規則は、のような式の効果を記述するには不十分(setf (aref x y) z)です。

  3. with-output-to-stringまた、マクロ内のコードのセマンティクスを変更します。同様に、with-current-bufferおよび他のwith-マクロの束。

  4. dotimes また、同様の機能を関数の観点から説明することはできません。

  5. ignore-errors ラップするコードのセマンティクスを変更します。

  6. Emacs Lispで使用されているcl-libパッケージ、eieioパッケージ、およびその他のほぼどこにでもあるライブラリには、マクロが多数定義されていますが、関数フォームは異なる解釈をしているように見えます。

そのため、マクロは、新しいセマンティクスを言語に導入するためのツールです。それを行う別の方法を考えることはできません(少なくともEmacs Lispでは)。


マクロを使用しない場合:

  1. 新しいセマンティクスを使用して新しいコンストラクトを導入しない場合(つまり、関数が同じように機能します)。

  2. これが単なる利便性の場合(つまり、短くするためにmvb展開される名前のマクロを作成するcl-multiple-value-bind)。

  3. 多くのエラー処理コードがマクロによって隠されると予想される場合(既に述べたように、マクロのデバッグは困難です)。


関数よりもマクロを強く好む場合:

  1. ドメイン固有の概念が関数呼び出しによって隠されている場合。たとえばcontext、連続して呼び出す必要がある一連の関数に同じ引数を渡す必要がある場合、context引数が隠されているマクロでそれらをラップすることをお勧めします。

  2. 関数形式の汎用コードが過度に冗長である場合(Emacs Lispのむき出しの繰り返し構造は私の好みには冗長すぎるため、プログラマを不必要に低レベルの実装の詳細にさらすことになります)。


以下は、コンピューターサイエンスのセマンティクスが何を意味しているのかを直感的に理解できるように意図された、骨抜きバージョンです。

これらの構文規則だけを備えた非常に単純なプログラミング言語があるとします。

variable := 1 | 0
operation := +
expression := variable | (expression operation expression)

この言語を使用すると、(1+0)+1or 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必ずしも関数であるとは限らないため、上記のセマンティックルールはいずれもこのケースに適用されないことに注意してください。


1
「構文糖」は、実際にはコンピューターサイエンスの用語ではありません。エンジニアがさまざまな意味で使用するものです。だから私は決定的な答えを持っていません。私たちはセマンティクスについて話しているので、フォームの構文を解釈するためのルール(x y)はおおよそ「yを評価してから、前の評価の結果でxを呼び出す」ことです。を記述すると(defun x y)、yは評価されず、xも評価されません。異なるセマンティクスを使用して同等の効果を持つコードを作成できるかどうかは、独自のセマンティクスを持つこのコードを否定しません。
wvxvw

1
あなたの2番目のコメントについて:あなたが主張していることを言っているあなたが使用しているマクロの定義を参照してください。マクロを使用して新しいセマンティックルールを導入する例を紹介しました。少なくともCSの正確な意味では。定義について議論することは生産的だとは思いませんし、CSで使用されている定義はこの議論に最適だと思います。
wvxvw

1
まあ...「セマンティクス」という言葉の意味を自分で考えているのは、皮肉なことですが、考えてみてください:)しかし、実際には、これらのものには非常に正確な定義があります。それらを無視します。私が答えでリンクした本は、CS対応コースで教えられている意味論に関する標準的な教科書です。Wikiの対応する記事en.wikipedia.org/wiki/Semantics_%28computer_science%29を読むだけで、実際の内容がわかります。この例は、(defun x y)関数アプリケーションを解釈するためのルールです。
wvxvw

まあ、これが私の議論の仕方だとは思いません。試してみるのも間違いでした。これには意味がないので、コメントを削除しました。時間を無駄にしてすみません。
lunaryorn
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.