Trình trang trí Python và macro Lisp


18

Khi tìm kiếm các nhà trang trí Python, ai đó đã đưa ra tuyên bố, rằng họ mạnh mẽ như các macro Lisp (đặc biệt là Clojure).

Nhìn vào các ví dụ được đưa ra trong PEP 318, có vẻ như chúng chỉ là một cách ưa thích để sử dụng các hàm bậc cao cũ đơn giản hơn trong 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):
    ...

Tôi chưa thấy bất kỳ mã nào biến đổi trong bất kỳ ví dụ nào, như được mô tả trong Anatomy of a Clojure Macro . Thêm vào đó, tính đồng nhất bị thiếu của Python có thể làm cho việc chuyển đổi mã không thể thực hiện được.

Vì vậy, làm thế nào để hai so sánh này và bạn có thể nói chúng gần bằng nhau trong những gì bạn có thể làm? Bằng chứng dường như chỉ ra chống lại nó.

Chỉnh sửa: Dựa trên một nhận xét, tôi đang tìm kiếm hai điều: so sánh trên mạnh mẽ như trên phạm vi mạnh mẽ và trên phạm vi dễ dàng để làm những điều tuyệt vời với phạm lỗi.


12
Tất nhiên các nhà trang trí không phải là macro thực sự. Họ không thể dịch một ngôn ngữ tùy ý (với một cú pháp hoàn toàn khác) thành python. Những người tuyên bố ngược lại chỉ đơn giản là không hiểu các macro.
SK-logic

1
Python không phải là homoiconic, tuy nhiên nó rất rất năng động. Homoiconicity chỉ được yêu cầu cho các phép biến đổi mã mạnh mẽ nếu bạn muốn thực hiện nó trong thời gian biên dịch - nếu bạn có hỗ trợ truy cập trực tiếp vào AST đã biên dịch và các công cụ để thay đổi nó, bạn có thể thực hiện nó trong thời gian chạy bất kể cú pháp ngôn ngữ. Điều đó đang được nói, "mạnh mẽ như" và "dễ dàng để làm những điều tuyệt vời với" là những khái niệm rất khác nhau.
Phoshi

Có lẽ tôi nên thay đổi câu hỏi thành một cách dễ dàng để làm những điều tuyệt vời với trực tuyến. ;)
Profpatsch

Có lẽ ai đó có thể hack một số hàm bậc cao hơn Clojure có thể so sánh với ví dụ Python ở trên. Tôi đã cố gắng nhưng vượt qua tâm trí của tôi trong quá trình này. Vì ví dụ Python sử dụng các thuộc tính đối tượng nên nó phải khác một chút.
Profpatsch

@Phoshi Thay đổi AST được biên dịch trong thời gian chạy được gọi là: mã tự sửa đổi .
Kaz

Câu trả lời:


16

Một trang trí về cơ bản chỉ là một chức năng .

Ví dụ trong Lisp chung:

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

Ở trên hàm là một ký hiệu (sẽ được trả về DEFUN) và chúng ta đặt các thuộc tính vào danh sách thuộc tính của ký hiệu .

Bây giờ chúng ta có thể viết nó xung quanh một định nghĩa hàm:

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

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

)  

Nếu chúng ta muốn thêm một cú pháp ưa thích như trong Python, chúng ta viết một macro đọc . Một macro người đọc cho phép chúng ta lập trình theo cấp độ cú pháp biểu thức s:

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

Sau đó chúng ta có thể viết:

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

Người đọc Lisp đọc ở trên để:

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

Bây giờ chúng tôi có một hình thức trang trí trong Common Lisp.

Kết hợp các macro và macro đọc.

Trên thực tế tôi sẽ thực hiện dịch trên mã thực bằng cách sử dụng macro chứ không phải hàm.

(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))))

Việc sử dụng là như trên với cùng một macro đọc. Ưu điểm là trình biên dịch Lisp vẫn xem nó như một dạng được gọi là cấp cao nhất - trình biên dịch tệp * xử lý các biểu mẫu cấp cao nhất, ví dụ: nó thêm thông tin về chúng vào môi trường thời gian biên dịch . Trong ví dụ trên, chúng ta có thể thấy rằng macro nhìn vào mã nguồn và trích xuất tên.

Trình đọc Lisp đọc ví dụ trên vào:

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

Mà sau đó được macro mở rộng thành:

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

Macro rất khác với macro đọc .

Macro nhận được mã nguồn được thông qua, có thể làm bất cứ điều gì họ muốn và sau đó trả lại mã nguồn. Nguồn đầu vào không cần phải là mã Lisp hợp lệ. Nó có thể là bất cứ điều gì và nó có thể được viết hoàn toàn khác nhau. Kết quả phải là mã Lisp hợp lệ sau đó. Nhưng nếu mã được tạo cũng sử dụng macro, thì cú pháp của mã được nhúng trong lệnh gọi macro lại có thể là một cú pháp khác. Một ví dụ đơn giản: người ta có thể viết một macro toán học chấp nhận một số loại cú pháp toán học:

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

Biểu thức y = 3 x ^ 2 - 4 x + 3không phải là mã Lisp hợp lệ, nhưng macro có thể phân tích cú pháp và trả về mã Lisp hợp lệ như thế này:

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

Có nhiều trường hợp sử dụng macro khác trong Lisp.


8

Trong Python (ngôn ngữ), các trình trang trí không thể sửa đổi hàm, chỉ bao bọc nó, vì vậy chúng chắc chắn ít mạnh hơn các macro lisp.

Trong CPython (trình thông dịch), các trình trang trí có thể sửa đổi hàm vì chúng có quyền truy cập vào mã byte, nhưng hàm này được biên dịch trước và có thể bị thay đổi bởi trình trang trí, do đó không thể thay đổi cú pháp, một điều không thể thiếu tương đương sẽ cần phải làm.

Lưu ý rằng các lisps hiện đại không sử dụng biểu thức S làm mã byte, do đó, macro hoạt động trên danh sách biểu thức S chắc chắn hoạt động trước khi biên dịch mã byte như đã lưu ý ở trên, trong python trình trang trí chạy sau nó.


1
Bạn không cần phải sửa đổi chức năng. Bạn chỉ cần đọc mã của hàm dưới một số hình thức (trong thực tế, điều này có nghĩa là mã byte). Không phải điều này làm cho nó thực tế hơn.

2
@delnan: Về mặt kỹ thuật, lisp cũng không sửa đổi nó; nó đang sử dụng nó làm nguồn để tạo một cái mới và python cũng vậy. Vấn đề nằm ở chỗ không có danh sách mã thông báo hoặc AST và thực tế trình biên dịch đã phàn nàn về một số điều mà bạn có thể cho phép trong macro.
Jan Hudec

4

Thật khó để sử dụng các trình trang trí Python để giới thiệu các cơ chế dòng điều khiển mới.

Việc sử dụng các macro Lisp thông thường để giới thiệu các cơ chế dòng điều khiển mới là rất quan trọng.

Từ điều này, có lẽ theo sau chúng không biểu cảm như nhau (tôi chọn diễn giải "mạnh mẽ" là "biểu cảm", vì tôi nghĩ rằng ý nghĩa thực sự của chúng).


Tôi dám nóis/quite hard/impossible/

@delnan Vâng, tôi sẽ không đi khá xa như vậy để nói "bất khả thi", nhưng bạn chắc chắn sẽ phải làm việc ở đó.
Vatine

0

Đó chắc chắn là liên quan đến chức năng, nhưng từ một trình trang trí Python, việc sửa đổi phương thức được gọi là không tầm thường (đó sẽ là ftham số trong ví dụ của bạn). Để sửa đổi nó, bạn có thể phát điên với mô-đun ast ), nhưng bạn sẽ tham gia vào một số chương trình khá phức tạp.

Những điều dọc theo dòng này đã được thực hiện: kiểm tra gói macropy cho một số ví dụ thực sự gây chú ý.


3
Ngay cả các astcông cụ định dạng trong python cũng không bằng các macro Lisp. Với Python, ngôn ngữ nguồn phải là Python, với macro Lisp, ngôn ngữ nguồn được biến đổi bởi một macro có thể, theo nghĩa đen, là bất cứ thứ gì. Do đó, siêu lập trình Python chỉ phù hợp với những thứ đơn giản (như AoP), trong khi siêu lập trình Lisp rất hữu ích để thực hiện các trình biên dịch eDSL mạnh mẽ.
SK-logic

1
Điều này là macropy không được thực hiện bằng cách sử dụng trang trí. Nó sử dụng cú pháp trang trí (vì nó phải sử dụng cú pháp python hợp lệ), nhưng nó được thực hiện bằng cách chiếm quyền điều khiển quá trình biên dịch byte từ một hook nhập.
Jan Hudec

@ SK-logic: Trong Lisp, ngôn ngữ nguồn cũng phải là ngôn ngữ chính. Chỉ cần cú pháp Lisp rất đơn giản nhưng linh hoạt, trong khi cú pháp python phức tạp hơn nhiều và không quá linh hoạt.
Jan Hudec

1
@JanHudec, trong ngôn ngữ nguồn Lisp có thể có bất kỳ cú pháp (tôi thực sự có nghĩa là bất kỳ ) - xem các macro đọc.
SK-logic
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.