Dekorator Python dan makro Lisp

18

Ketika mencari dekorator Python seseorang membuat pernyataan, bahwa mereka sekuat macro Lisp (terutama Clojure).

Melihat contoh-contoh yang diberikan dalam PEP 318 bagi saya seolah-olah itu hanya cara mewah untuk menggunakan fungsi-fungsi tingkat tinggi lama yang biasa di 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):
    ...

Saya belum melihat kode yang berubah dalam salah satu contoh, seperti dijelaskan dalam Anatomi Makro Clojure . Ditambah lagi, homoikonisitas Python yang hilang dapat membuat transformasi kode menjadi tidak mungkin.

Jadi, bagaimana keduanya membandingkan dan dapatkah Anda mengatakan mereka setara dalam hal apa yang dapat Anda lakukan? Bukti tampaknya menunjukkan hal yang berlawanan.

Sunting: Berdasarkan pada komentar, saya mencari dua hal: perbandingan pada "sekuat" dan "semudah melakukan hal-hal yang luar biasa dengan".

Profpatsch
sumber
12
Tentu saja dekorator bukanlah makro asli. Mereka tidak dapat menerjemahkan bahasa yang berubah-ubah (dengan sintaks yang sama sekali berbeda) menjadi python. Mereka yang mengklaim sebaliknya tidak mengerti makro.
SK-logic
1
Python tidak homoikonik, namun sangat dinamis. Homoiconicity hanya diperlukan untuk transformasi kode yang kuat jika Anda ingin melakukannya pada waktu kompilasi - jika Anda memiliki dukungan untuk akses langsung ke AST yang dikompilasi dan alat untuk mengubahnya, Anda dapat melakukannya pada saat runtime terlepas dari sintaks bahasa. Yang sedang berkata, "sekuat" dan "mudah melakukan hal-hal luar biasa dengan" adalah konsep yang sangat berbeda.
Phoshi
Mungkin saya harus mengubah pertanyaan menjadi "semudah melakukan hal-hal yang luar biasa dengan" kemudian. ;)
Profpatsch
Mungkin seseorang dapat meretas beberapa fungsi tingkat tinggi Clojure yang sebanding dengan contoh Python di atas. Saya mencoba tetapi menyilangkan pikiran saya dalam proses itu. Karena contoh Python menggunakan atribut objek ini harus sedikit berbeda.
Profpatsch
@ Phoshi Mengubah AST yang dikompilasi pada saat run-time dikenal sebagai: kode modifikasi diri .
Kaz

Jawaban:

16

Sebuah dekorator pada dasarnya hanya fungsi .

Contoh dalam Common Lisp:

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

Dalam fungsi di atas adalah simbol (yang akan dikembalikan oleh DEFUN) dan kami menempatkan atribut pada daftar properti simbol .

Sekarang kita dapat menuliskannya di sekitar definisi fungsi:

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

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

)  

Jika kita ingin menambahkan sintaksis mewah seperti di Python, kita menulis makro pembaca . Makro pembaca memungkinkan kita untuk memprogram pada tingkat sintaks s-ekspresi:

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

Kami kemudian dapat menulis:

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

Pembaca Lisp membaca di atas untuk:

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

Sekarang kami memiliki bentuk dekorator di Common Lisp.

Menggabungkan macro dan macro pembaca.

Sebenarnya saya akan melakukan terjemahan di atas dalam kode nyata menggunakan makro, bukan fungsi.

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

Penggunaannya seperti di atas dengan pembaca makro yang sama. Keuntungannya adalah bahwa kompilator Lisp masih melihatnya sebagai apa yang disebut formulir tingkat atas - kompiler file * memperlakukan formulir tingkat atas secara khusus, misalnya menambahkan informasi tentang mereka ke dalam lingkungan waktu kompilasi . Pada contoh di atas kita dapat melihat bahwa makro melihat ke dalam kode sumber dan mengekstrak namanya.

Pembaca Lisp membaca contoh di atas menjadi:

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

Yang kemudian diperluas makro menjadi:

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

Makro sangat berbeda dari makro pembaca .

Makro mendapatkan kode sumber lulus, dapat melakukan apa pun yang mereka inginkan dan kemudian mengembalikan kode sumber. Sumber input tidak perlu kode Lisp yang valid. Itu bisa apa saja dan bisa ditulis sama sekali berbeda. Hasilnya harus kode Lisp yang valid. Tetapi jika kode yang dihasilkan menggunakan makro juga, maka sintaks dari kode yang tertanam dalam panggilan makro lagi bisa menjadi sintaks yang berbeda. Contoh sederhana: seseorang dapat menulis makro matematika yang akan menerima semacam sintaksis matematika:

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

Ekspresi y = 3 x ^ 2 - 4 x + 3ini bukan kode Lisp yang valid, tetapi makro misalnya dapat menguraikannya dan mengembalikan kode Lisp yang valid seperti ini:

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

Ada banyak kasus penggunaan makro di Lisp.

Rainer Joswig
sumber
8

Dalam Python (bahasa) dekorator tidak dapat memodifikasi fungsi, hanya membungkusnya, jadi mereka jelas jauh lebih kuat daripada macro lisp.

Dalam CPython (interpreter) dekorator dapat memodifikasi fungsi karena mereka memiliki akses ke bytecode, tetapi fungsi tersebut dikompilasi terlebih dahulu dan kemudian dapat dipermainkan oleh dekorator, sehingga tidak mungkin untuk mengubah sintaksis, suatu hal lisp-macro -Seivalen perlu dilakukan.

Perhatikan, bahwa lisps modern tidak menggunakan ekspresi-S sebagai bytecode, jadi makro yang bekerja pada daftar ekspresi-S pasti bekerja sebelum kompilasi bytecode seperti yang disebutkan di atas, dengan python dekorator berjalan setelahnya.

Jan Hudec
sumber
1
Anda tidak perlu memodifikasi fungsinya. Anda hanya perlu membaca kode fungsi dalam beberapa bentuk (dalam praktiknya, ini berarti bytecode). Bukan berarti ini membuatnya lebih praktis.
2
@delnan: Secara teknis, lisp juga tidak memodifikasinya; itu menggunakannya sebagai sumber untuk menghasilkan yang baru dan begitu juga python, ya. Masalahnya terletak pada tidak adanya token list atau AST dan fakta kompiler sudah mengeluh tentang beberapa hal yang mungkin Anda izinkan di makro.
Jan Hudec
4

Sangat sulit untuk menggunakan dekorator Python untuk memperkenalkan mekanisme aliran kontrol baru.

Berbatasan-dengan-sepele untuk menggunakan macro Lisp Umum untuk memperkenalkan mekanisme aliran kontrol baru.

Dari sini, mungkin berarti mereka tidak sama ekspresifnya (saya memilih untuk menafsirkan "kuat" sebagai "ekspresif", karena saya berpikir apa yang sebenarnya mereka maksudkan).

Vatine
sumber
Saya berani mengatakans/quite hard/impossible/
@delnan Yah, aku tidak akan pergi cukup sejauh untuk mengatakan "tidak mungkin", tapi Anda pasti harus bekerja di itu.
Vatine
0

Ini tentu saja berkaitan dengan functionallity, tetapi dari dekorator Python tidak sepele untuk memodifikasi metode yang dipanggil (itu akan menjadi fparameter dalam contoh Anda). Untuk memodifikasinya, Anda bisa menjadi gila dengan modul ast ), tetapi Anda akan menyukai beberapa pemrograman yang cukup rumit.

Hal-hal di sepanjang garis ini telah dilakukan: periksa paket makropi untuk beberapa contoh yang benar-benar membekas .

Jacob Oscarson
sumber
3
Bahkan asthal -transformasi dalam python tidak sama dengan macro Lisp. Dengan Python, bahasa sumber haruslah Python, dengan Lisp macro bahasa sumber yang diubah oleh makro bisa, secara harfiah, apa saja. Oleh karena itu, metaprogramming Python hanya cocok untuk hal-hal sederhana (seperti AoP), sedangkan metaprogramming Lisp berguna untuk mengimplementasikan kompiler eDSL yang kuat.
SK-logic
1
Masalahnya adalah bahwa macropy tidak diimplementasikan menggunakan dekorator. Ia menggunakan sintaksis dekorator (karena itu harus menggunakan sintaksis python yang valid), tetapi diimplementasikan dengan membajak proses kompilasi byte dari hook impor.
Jan Hudec
@ SK-logic: Dalam Lisp bahasa sumber juga harus lisp. Hanya sintaks Lisp sangat sederhana tetapi fleksibel, sedangkan sintaksis python jauh lebih kompleks dan tidak begitu fleksibel.
Jan Hudec
1
@ JanHudec, dalam bahasa sumber Lisp dapat memiliki sintaks (maksud saya, semua ) - lihat makro pembaca.
SK-logic