PythonデコレータとLispマクロ


18

Pythonデコレータを見ると、誰かがLispマクロ(特にClojure)と同じくらい強力だという声明を出しました。

PEP 318で与えられた例を見ると、まるでそれらがLispで単純な古い高階関数を使うための単なるおしゃれな方法であるかのように見えます:

def attrs(**kwds):
    def decorate(f):
        for k in kwds:
            setattr(f, k, kwds[k])
        return f
    return decorate

@attrs(versionadded="2.2",
       author="Guido van Rossum")
def mymethod(f):
    ...

Clojureマクロの構造」で説明されているように、どの例でもコードの変換は見ていません。さらに、Pythonの欠落した同質 により、コード変換が不可能になる可能性があります。

それで、これら2つはどのように比較されますか。証拠はそれに反対するようです。

編集:コメントに基づいて、私は2つのことを探しています:「できるだけ強力」と「素晴らしいものを簡単に実行できる」との比較。


12
もちろん、デコレータは実際のマクロではありません。任意の言語(まったく異なる構文)をPythonに翻訳することはできません。反対を主張する人は、単にマクロを理解していません。
SKロジック

1
Pythonは同型ではありませんが、非常に動的です。コンパイル時にそれを行いたい場合、同質性は強力なコード変換にのみ必要です。コンパイルされたASTおよびそれを変更するツールへの直接アクセスをサポートしている場合、言語構文に関係なく実行時に行うことができます。とはいえ、「できるだけ強力」と「素晴らしいことを簡単に行える」は、非常に異なる概念です。
-Phoshi

質問を「簡単にできること」に変更する必要があるかもしれません。;)
Profpatsch

たぶん、誰かが上記のPythonの例と同等のClojure高階関数をハックできるかもしれません。私は試しましたが、その過程で心を交差させました。Pythonの例ではオブジェクト属性を使用しているため、これは少し異なる必要があります。
-Profpatsch

@Phoshi実行時にコンパイル済みASTを変更することは、自己変更コードとして知られています
カズ

回答:


16

デコレータは、基本的にはある機能

Common Lispの例:

(defun attributes (keywords function)
  (loop for (key value) in keywords
        do (setf (get function key) value))
  function)

上記では、関数は(によって返されるDEFUN)シンボルであり、属性をシンボルのプロパティリストに配置します。

これで関数定義の周りにそれを書くことができます:

(attributes
  '((version-added "2.2")
    (author "Rainer Joswig"))

  (defun foo (a b)
    (+ a b))

)  

Pythonのような派手な構文を追加する場合は、リーダーマクロを作成します。リーダーマクロを使用すると、s-expression構文のレベルでプログラミングできます。

(set-macro-character
 #\@
 (lambda (stream char)
   (let ((decorator (read stream))
         (arg       (read stream))
         (form      (read stream)))
     `(,decorator ,arg ,form))))

その後、次のように記述できます。

@attributes'((version-added "2.2")
             (author "Rainer Joswig"))
(defun foo (a b)
  (+ a b))

Lisp リーダーは上記を読んで:

(ATTRIBUTES (QUOTE ((VERSION-ADDED "2.2")
                    (AUTHOR "Rainer Joswig")))
            (DEFUN FOO (A B) (+ A B)))

これでCommon Lisp にデコレータのフォームができました。

マクロとリーダーマクロの組み合わせ。

実際、関数ではなくマクロを使用して、実際のコードで上記の変換を行います。

(defmacro defdecorator (decorator arg form)
  `(progn
     ,form
     (,decorator ,arg ',(second form))))

(set-macro-character
 #\@
 (lambda (stream char)
   (declare (ignore char))
   (let* ((decorator (read stream))
          (arg       (read stream))
          (form      (read stream)))
     `(defdecorator ,decorator ,arg ,form))))

同じリーダーマクロを使用した場合の使用方法は上記のとおりです。利点は、Lispコンパイラーがまだそれをいわゆるトップレベルフォームと見なしていることです。* fileコンパイラーはトップレベルフォームを特別に扱います。たとえば、それらの情報をコンパイル時環境に追加します。上記の例では、マクロがソースコードを調べて名前を抽出していることがわかります。

Lisp リーダーは上記の例を次のように読み込みます:

(DEFDECORATOR ATTRIBUTES
  (QUOTE ((VERSION-ADDED "2.2")
           (AUTHOR "Rainer Joswig")))
  (DEFUN FOO (A B) (+ A B)))

マクロは次のように展開されます:

(PROGN (DEFUN FOO (A B) (+ A B))
       (ATTRIBUTES (QUOTE ((VERSION-ADDED "2.2")
                           (AUTHOR "Rainer Joswig")))
                   (QUOTE FOO)))

マクロは、リーダーマクロとは大きく異なります

マクロはソースコードを渡され、必要な処理を行ってからソースコードを返します。入力ソースは有効なLispコードである必要はありません。何でもかまいませんし、まったく違うように書くこともできます。結果は有効なLispコードでなければなりません。ただし、生成されたコードがマクロも使用している場合、マクロ呼び出しに埋め込まれたコードの構文は、別の構文になる可能性があります。簡単な例:ある種の数学的構文を受け入れる数学マクロを書くことができます:

(math y = 3 x ^ 2 - 4 x + 3)

y = 3 x ^ 2 - 4 x + 3は有効なLispコードではありませんが、マクロはたとえばそれを解析し、次のような有効なLispコードを返すことができます。

(setq y (+ (* 3 (expt x 2))
           (- (* 4 x))
           3))

Lispにはマクロの他の多くのユースケースがあります。


8

Python(言語)では、デコレータは関数を変更できず、ラップするだけであるため、Lispマクロよりも明らかに強力ではありません。

CPython(インタープリター)では、デコレータはバイトコードにアクセスできるため、関数を変更できますが、関数は最初にコンパイルされ、デコレータで調整することができるため、構文を変更することはできません。 -同等のことを行う必要があります。

最新のlispsはバイトコードとしてS-expressionを使用しないため、S-expressionリストで動作するマクロは、上記のようにバイトコードのコンパイル前に確実に機能します。Pythonでは、デコレータがそれを実行します。


1
関数を変更する必要はありません。関数のコードを何らかの形式で読み取るだけで済みます(実際には、これはバイトコードを意味します)。これがより実用的になるわけではありません。

2
@delnan:技術的には、lispも修正していません。それをソースとして使用して新しいものを生成しているので、Pythonもそうです。問題は、トークンリストまたはASTが存在しないことと、コンパイラがマクロで許可しない可能性があることについて既に文句を言っているという事実にあります。
1月Hudec

4

Pythonのデコレータを使用して新しい制御フローメカニズムを導入することは非常に困難です。

Common Lispマクロを使用して新しい制御フローメカニズムを導入することは、些細なことです。

このことから、おそらくそれらは等しく表現的ではないということになります(私は「強力」を「表現的」と解釈することを選択します。


あえて言うs/quite hard/impossible/

まあ@delnan、私は行くだろう、かなりこれまで「不可能」と言うするが、あなたは間違いなくそれで仕事をする必要があるだろう。
バティーン

0

それは確かに機能性に関連していますが、Pythonデコレータからは、呼び出されているメソッドを変更するのは簡単ではありません(これはfあなたの例ではパラメータになります)。それを修正するには、astモジュールに夢中になるかもしれません)、しかし、あなたはかなり複雑なプログラミングをすることになります。

しかし、この線に沿って物事が行われました:いくつかの本当に心を曲げる例については、macropyパッケージをチェックしてください。


3
astPython の-transformingでさえ、Lispマクロとは異なります。Pythonでは、ソース言語はPythonである必要があります。Lispマクロでは、マクロによって変換されるソース言語は文字通り何でもかまいません。したがって、Pythonメタプログラミングは単純なもの(AoPなど)にのみ適していますが、Lispメタプログラミングは強力なeDSLコンパイラの実装に役立ちます。
SKロジック

1
大事なのは、デコレータを使用してmacropyが実装されていないことです。デコレータ構文を使用します(有効なpython構文を使用する必要があるため)が、インポートフックからバイトコンパイルプロセスをハイジャックすることで実装されます。
ジャン・ヒューデック

@ SK-logic:Lispでは、ソース言語もLispでなければなりません。ちょうどLisp構文は非常に単純ですが柔軟ですが、Python構文ははるかに複雑でそれほど柔軟ではありません。
ジャン・ヒューデック

1
@JanHudecは、Lispソース言語では、任意の(実際には任意の)構文を持つことができます-リーダーマクロを参照してください。
SKロジック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.