335

プログラミング言語に関する Paul Graham のエッセイを読むと、 Lisp マクロが唯一の方法だと思うでしょう。多忙な開発者として、他のプラットフォームで作業しているため、Lisp マクロを使用する特権はありませんでした。バズを理解したい人として、この機能が非常に強力な理由を説明してください.

また、これを Python、Java、C#、または C 開発の世界から理解できるものに関連付けてください。

4

15 に答える 15

351

簡単に言うと、マクロはCommonLispまたはドメイン固有言語(DSL)の言語構文拡張を定義するために使用されます。これらの言語は、既存のLispコードに直接埋め込まれています。現在、DSLはLispに似た構文(PeterNorvigのCommonLisp用Prologインタープリターなど)または完全に異なる構文( Clojureの中置記法など)を持つことができます。

より具体的な例を次に示します
。Pythonには、言語に組み込まれたリスト内包表記があります。これにより、一般的なケースの簡単な構文が得られます。この線

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

0から9までのすべての偶数を含むリストを生成します。Python1。5日間には、そのような構文はありませんでした。次のようなものを使用します。

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

これらは両方とも機能的に同等です。不信の停止を呼び出して、Lispには非常に限定されたループマクロがあり、反復を行うだけで、リスト内包に相当する簡単な方法はありません。

Lispでは次のように書くことができます。この不自然な例は、Lispコードの良い例ではなくPythonコードと同一であるように選択されていることに注意してください。

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

先に進む前に、マクロとは何かをよりよく説明する必要があります。これは、コードごとに実行される変換です。つまり、インタープリター(またはコンパイラー)によって読み取られ、引数としてコードを取り込んだコードが操作されて結果が返され、その結果がインプレースで実行されます。

もちろん、それは多くのタイピングであり、プログラマーは怠惰です。したがって、リスト内包を行うためのDSLを定義できます。実際、すでに1つのマクロ(ループマクロ)を使用しています。

Lispはいくつかの特別な構文形式を定義しています。引用符(')は、次のトークンがリテラルであることを示します。準引用符またはバックティック(`)は、次のトークンがエスケープ付きのリテラルであることを示します。エスケープはコンマ演算子で示されます。リテラル'(1 2 3)はPythonのと同等です[1, 2, 3]。別の変数に割り当てるか、その場で使用できます。以前に定義された変数で`(1 2 ,x)あるPythonの[1, 2, x]whereと同等のものと考えることができます。xこのリスト表記は、マクロに入る魔法の一部です。2番目の部分はLispリーダーで、コードの代わりにマクロをインテリジェントに置き換えますが、以下に最もよく示されています。

lcompしたがって、 (リスト内包表記の略)というマクロを定義できます。その構文は、例で使用したpythonとまったく同じになります[x for x in range(10) if x % 2 == 0] -(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

これで、コマンドラインで実行できます。

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

かなりきちんとしていますね 今ではそれだけではありません。必要に応じて、メカニズムまたはペイントブラシがあります。必要に応じて、任意の構文を使用できます。PythonやC#のwith構文のように。または.NETのLINQ構文。結局、これが人々をLispに引き付けるものです-究極の柔軟性。

于 2011-01-07T01:56:36.450 に答える
109

ここで Lisp マクロに関する包括的な議論を見つけることができます。

その記事の興味深いサブセット:

ほとんどのプログラミング言語では、構文は複雑です。マクロは、プログラムの構文を分解し、分析し、再構築する必要があります。彼らはプログラムのパーサーにアクセスできないため、ヒューリスティックと最善の推測に頼らなければなりません。カットレートの分析が間違っていると、壊れてしまうことがあります。

しかし、Lisp は違います。Lisp マクロパーサーにアクセスできます。これは非常に単純なパーサーです。 Lisp プログラムのソースは文字列ではないため、Lisp マクロは文字列ではなく、リスト形式の事前に解析されたソース コードの一部として渡されます。それはリストです。Lisp プログラムは、リストを分解して元に戻すのが非常に得意です。彼らはこれを毎日確実に行っています。

これは拡張された例です。Lisp には、代入を実行する「setf」と呼ばれるマクロがあります。setf の最も単純な形式は次のとおりです。

  (setf x whatever)

これは、記号 "x" の値を式 "whatever" の値に設定します。

Lisp にはリストもあります。「car」関数と「cdr」関数を使用して、それぞれリストの最初の要素またはリストの残りの要素を取得できます。

リストの最初の要素を新しい値に置き換えたい場合はどうすればよいでしょうか。それを行うための標準機能があり、信じられないことに、その名前は「車」よりもさらに悪いです。「rplaca」です。でも「rplaca」は覚える必要はありません。

  (setf (car somelist) whatever)

ソリストの車をセットします。

ここで実際に起こっていることは、「setf」がマクロであるということです。コンパイル時に引数を調べ、最初の引数の形式が (car SOMETHING) であることを確認します。「ああ、プログラマーは何かの車を設定しようとしています。そのために使用する関数は 'rplaca' です。」そして、コードを次のように静かに書き換えます。

  (rplaca somelist whatever)
于 2008-11-06T07:45:13.620 に答える
58

Common Lisp マクロは、基本的にコードの「構文プリミティブ」を拡張します。

たとえば、C では、switch/case コンストラクトは整数型でのみ機能し、float または文字列に使用する場合は、ネストされた if ステートメントと明示的な比較が残されます。また、C マクロを作成して作業を行う方法もありません。

しかし、Lisp マクロは (本質的に) コードのスニペットを入力として取り、マクロの「呼び出し」を置き換えるコードを返す Lisp プログラムであるため、「プリミティブ」のレパートリーを必要なだけ拡張することができ、通常は終了します。より読みやすいプログラムで。

C で同じことを行うには、最初の (完全な C ではない) ソースを食べて、C コンパイラが理解できるものを吐き出すカスタム プリプロセッサを作成する必要があります。それは間違った方法ではありませんが、必ずしも最も簡単な方法ではありません。

于 2008-11-06T09:39:50.660 に答える
47

Lisp マクロを使用すると、任意の部分または式をいつ評価するか (もしあれば) を決定できます。簡単な例を挙げると、C を考えてみます。

expr1 && expr2 && expr3 ...

これが何を言っているのか: Evaluate expr1、そして true の場合は evaluateexpr2など。

これ&&を関数にしてみます...そうです、できません。次のような呼び出し:

and(expr1, expr2, expr3)

falseexprsかどうかに関係なく、答えを出す前に3 つすべてを評価します。expr1

Lisp マクロを使用すると、次のようなコードを作成できます。

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

これ&&で、関数と同じように呼び出すことができ、渡されたフォームがすべて true でない限り評価されません。

これがどのように役立つかを確認するには、以下を比較してください。

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

と:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

マクロを使ってできるその他のことは、新しいキーワードやミニ言語を作成すること ((loop ...)例としてマクロをチェックしてください)、他の言語を Lisp に統合することです。たとえば、次のようなことを言うマクロを書くことができます:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

そして、それはReader マクロにも入りません。

お役に立てれば。

于 2008-11-06T09:21:51.790 に答える
33

私は、Lisp マクロがこの仲間よりもよく説明されているのを見たことがないと思います: http://www.defmacro.org/ramblings/lisp.html

于 2011-01-07T00:58:55.527 に答える
10

マクロとテンプレートを使用してCまたはC++で何ができるかを考えてください。これらは反復的なコードを管理するための非常に便利なツールですが、非常に厳しい方法で制限されています。

  • 制限されたマクロ/テンプレート構文は、それらの使用を制限します。たとえば、クラスまたは関数以外のものに拡張するテンプレートを作成することはできません。マクロとテンプレートは、内部データを簡単に維持できません。
  • CおよびC++の複雑で非常に不規則な構文により、非常に一般的なマクロを作成することが困難になります。

LispおよびLispマクロはこれらの問題を解決します。

  • LispマクロはLispで書かれています。あなたはマクロを書くためのLispの全力を持っています。
  • Lispの構文は非常に規則的です。

C ++をマスターしている人と話して、テンプレートメタプログラミングを行うために必要なすべてのテンプレートファッジを学習するのにどれくらいの時間を費やしたかを尋ねます。または、 Modern C ++ Designのような(優れた)本のすべてのクレイジーなトリックは、言語が10年間標準化されていても、デバッグが難しく、(実際には)実際のコンパイラ間で移植できません。メタプログラミングに使用する言語がプログラミングに使用する言語と同じである場合、そのすべてが溶けてしまいます。

于 2008-11-06T11:32:01.433 に答える
9

Lisp マクロは、プログラムの断片を入力として受け取ります。このプログラム フラグメントは、任意の方法で操作および変換できるデータ構造で表されます。最後に、マクロは別のプログラム フラグメントを出力し、このフラグメントが実行時に実行されます。

C# にはマクロ機能がありませんが、コンパイラがコードを CodeDOM ツリーに解析し、それをメソッドに渡し、これを別の CodeDOM に変換し、それを IL にコンパイルした場合と同等です。

for eachこれは、 -statement using-clause、linq -expressionsなどの "sugar" 構文selectを、基になるコードに変換するマクロとして実装するために使用できます。

Java にマクロがあれば、Sun がベース言語を変更しなくても、Java で Linq 構文を実装できます。

using以下は、実装用の C# の Lisp スタイル マクロがどのように見えるかを示す疑似コードです。

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }
于 2008-11-06T10:27:52.123 に答える
8

みんなの(素晴らしい)投稿に洞察を加えることができるかどうかはわかりませんが...

Lisp構文の性質上、Lispマクロはうまく機能します。

Lispは非常に正規言語です(すべてがリストであると考えてください)。マクロを使用すると、データとコードを同じものとして扱うことができます(lisp式を変更するために文字列の解析やその他のハックは必要ありません)。これらの2つの機能を組み合わせると、コードを変更するための非常にクリーンな方法が得られます。

編集:私が言おうとしていたのは、Lispは同像性であるということです。つまり、lispプログラムのデータ構造はlisp自体で記述されています。

そのため、言語自体を最大限に活用して、言語の上に独自のコードジェネレーターを作成する方法ができあがります(たとえば、Javaでは、バイトコードウィービングでハックする必要がありますが、AspectJなどの一部のフレームワークでは次のことができます。別のアプローチを使用してこれを行う、それは基本的にハックです)。

実際には、マクロを使用すると、追加の言語やツールを学ぶ必要がなく、言語自体の全力を使用して、lispの上に独自のミニ言語を構築することになります。

于 2008-11-06T12:12:39.010 に答える
6

Lisp マクロは、ほとんどすべての大規模なプログラミング プロジェクトで発生するパターンを表しています。最終的に、大規模なプログラムでは、ソースコードをテキストとして出力するプログラムを作成して貼り付けることができるプログラムを作成する方が簡単でエラーが発生しにくいことに気付くコードの特定のセクションがあります。

Python オブジェクトには と の 2 つのメソッドが__repr__あり__str__ます。 __str__は単に人間が読める表現です。 __repr__有効な Python コードである表現を返します。つまり、有効な Python としてインタープリターに入力できるものです。このようにして、実際のソースに貼り付けることができる有効なコードを生成する Python の小さなスニペットを作成できます。

Lisp では、このプロセス全体がマクロ システムによって形式化されています。確かに、構文の拡張機能を作成したり、あらゆる種類の派手なことを実行したりできますが、実際の有用性は上記に要約されています。もちろん、Lisp マクロ システムを使用すると、これらの「スニペット」を言語全体のフル パワーで操作できることが役立ちます。

于 2008-11-06T14:31:30.603 に答える
5

つまり、マクロはコードの変換です。それらは、多くの新しい構文構造を導入することを可能にします。たとえば、C# の LINQ を考えてみましょう。Lisp には、マクロによって実装される同様の言語拡張があります (組み込みのループ構造、反復など)。マクロにより、コードの重複が大幅に減少します。マクロを使用すると、「小さな言語」を埋め込むことができます (たとえば、c#/java では構成に xml を使用しますが、Lisp では同じことがマクロで実現できます)。マクロは、ライブラリの使用に関する問題を隠している場合があります。

たとえば、Lispでは次のように書くことができます

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

これにより、すべてのデータベースのもの (トランザクション、適切な接続のクローズ、データのフェッチなど) が隠されますが、C# では、SqlConnections、SqlCommands を作成し、SqlParameters を SqlCommands に追加し、SqlDataReaders をループして、それらを適切に閉じる必要があります。

于 2008-11-06T12:27:01.027 に答える
0

これは The common lisp cookbook から入手しましたが、Lisp マクロが優れている理由をうまく説明していると思います。

「マクロは、別の推定 Lisp コード上で動作し、実行可能な Lisp (に近いバージョン) に変換する通常の Lisp コードです。少し複雑に聞こえるかもしれません。簡単な例を挙げましょう。 2 つの変数を同じ値に設定する setq のバージョンです。

(setq2 x y (+ z 3))

x と y の両方が 11 に設定されている場合z=8(これを使用する方法は思いつきませんが、単なる例です)。

setq2 を関数として定義できないことは明らかです。と の場合x=50y=-5この関数は値 50、-5、および 11 を受け取ります。どの変数が設定されることになっているのかはわかりません。私たちが本当に言いたいのは、あなた (Lisp システム) が を見たら(setq2 v1 v2 e)、それを と同等のものとして扱うということ(progn (setq v1 e) (setq v2 e))です。実際、これはあまり正しくありませんが、今のところは問題ありません。(setq2 v1 v2 e)マクロを使用すると、入力パターンを「出力パターン」に変換するプログラムを指定することで、これを正確に行うことができます(progn ...)

これがいいと思ったら、ここで読み続けることができます: http://cl-cookbook.sourceforge.net/macros.html

于 2013-04-02T16:49:18.667 に答える
-4

Pythonにはデコレータがあり、基本的に別の関数を入力として受け取る関数があります。関数を呼び出したり、何か他のことをしたり、関数呼び出しをリソース取得リリースにラップしたりなど、好きなことをすることができますが、その関数の内部を覗くことはできません。デコレーターが関数のコードをリストとして受け取った場合、関数をそのまま実行できるだけでなく、関数の一部を実行したり、関数の行を並べ替えたりすることができます。

于 2011-01-07T17:02:44.060 に答える