Kapan menggunakan makro atau tidak menggunakan [ditutup]

12

Kapan saya harus menggunakan makro dalam program saya atau tidak?

Pertanyaan ini terinspirasi oleh jawaban informatif oleh @tarsius. Saya percaya banyak yang memiliki pengalaman hebat untuk berbagi dengan orang lain. Saya harap pertanyaan ini menjadi salah satu Pertanyaan Subjektif Hebat dan membantu programmer Emacs.

Yasushi Shoji
sumber

Jawaban:

13

Aturan praktis

Gunakan makro untuk sintaks , tetapi tidak pernah untuk semantik.

Tidak apa-apa menggunakan makro untuk beberapa gula sintaksis, tapi itu ide yang buruk untuk memasukkan logika ke dalam tubuh makro terlepas dari apakah itu dalam makro itu sendiri atau dalam ekspansi. Jika Anda merasa perlu melakukan itu, tuliskan fungsi, dan "makro pendamping" untuk gula sintaksis. Secara sederhana, jika Anda memiliki letmakro Anda sedang mencari masalah.

Mengapa?

Makro memiliki banyak sekali kekurangan:

  • Mereka berkembang pada waktu kompilasi dan karenanya harus ditulis dengan hati-hati untuk menjaga kompatibilitas biner di beberapa versi. Misalnya, menghapus fungsi atau variabel yang digunakan dalam ekspansi makro akan memecah kode byte dari semua paket dependen yang menggunakan makro ini. Dengan kata lain, jika Anda mengubah implementasi makro dengan cara yang tidak kompatibel, semua paket dependen harus dikompilasi ulang. Dan kompatibilitas biner tidak selalu jelas ...
  • Anda harus berhati-hati untuk mempertahankan urutan evaluasi intuitif . Semuanya mudah untuk menulis makro yang mengevaluasi formulir terlalu sering, atau pada waktu yang salah, atau di tempat yang salah, dll ...
  • Anda harus berhati-hati dengan semantik pengikatan : Makro mewarisi konteks pengikatannya dari situs ekspansi , yang berarti bahwa Anda tidak tahu apriori apa variabel leksikal yang ada, dan apakah leksikal mengikat bahkan tersedia. Makro harus bekerja dengan kedua semantik yang mengikat.
  • Terkait dengan ini, Anda harus berhati-hati saat menulis makro yang membuat penutupan . Ingat, Anda mendapatkan ikatan dari situs ekspansi, bukan dari definisi leksikal, jadi perhatikan apa yang Anda tutup dan bagaimana Anda membuat penutupan (ingat, Anda tidak tahu apakah ikatan leksikal aktif di situs ekspansi dan apakah Anda bahkan memiliki penutupan tersedia).
lunaryorn
sumber
1
Catatan tentang leksikal mengikat vs ekspansi makro sangat menarik; terima kasih lunaryorn. (Saya tidak pernah mengaktifkan pengikatan leksikal untuk kode apa pun yang saya tulis, jadi ini bukan konflik yang pernah saya pikirkan.)
phils
6

Dari pengalaman saya membaca, men-debug dan memodifikasi kode Elisp saya dan orang lain, saya sarankan untuk menghindari macro sebanyak mungkin.

Yang jelas tentang makro adalah mereka tidak mengevaluasi argumen mereka. Yang berarti bahwa jika Anda memiliki ekspresi kompleks yang dibungkus dengan makro, Anda tidak tahu apakah itu akan dievaluasi atau tidak, dan dalam konteks mana, kecuali jika Anda mencari definisi makro. Ini mungkin bukan masalah besar bagi Anda sendiri, karena Anda tahu definisi makro Anda sendiri (saat ini, mungkin tidak beberapa tahun kemudian).

Kerugian ini tidak berlaku untuk fungsi: semua argumen dievaluasi sebelum panggilan fungsi. Yang berarti Anda dapat membangun kompleksitas eval dalam situasi debugging yang tidak diketahui. Anda bahkan dapat langsung melompati definisi fungsi yang tidak diketahui oleh Anda, selama mereka mengembalikan data langsung, dan Anda menganggap bahwa mereka tidak menyentuh keadaan global.

Untuk menulis ulang dengan cara lain, makro menjadi bagian dari bahasa. Jika Anda membaca kode dengan makro yang tidak dikenal, Anda hampir membaca kode dalam bahasa yang tidak Anda ketahui.

Pengecualian untuk pemesanan di atas:

Makro standar seperti push, pop, dolistlakukan benar-benar banyak untuk Anda, dan dikenal untuk programmer lain. Mereka pada dasarnya bagian dari spesifikasi bahasa. Tapi praktis tidak mungkin untuk menstandarisasi makro Anda sendiri. Tidak hanya sulit untuk menghasilkan sesuatu yang sederhana dan berguna seperti contoh di atas, tetapi lebih sulit lagi adalah meyakinkan setiap programmer untuk mempelajarinya.

Juga, saya tidak keberatan makro seperti save-selected-window: mereka menyimpan dan mengembalikan keadaan dan mengevaluasi argumen mereka dengan cara yang sama progn. Cukup mudah, sebenarnya membuat kode lebih sederhana.

Contoh lain dari makro OK bisa seperti defhydraatau use-package. Makro ini pada dasarnya datang dengan bahasa mini mereka sendiri. Dan mereka masih memperlakukan data sederhana, atau bertindak seperti progn. Plus, mereka biasanya berada di tingkat atas, jadi tidak ada variabel pada tumpukan panggilan untuk bergantung pada argumen makro, membuatnya sedikit lebih sederhana.

Contoh makro yang buruk, menurut saya, adalah magit-section-actiondan magit-section-casedi Magit. Yang pertama sudah dihapus, tetapi yang kedua masih tersisa.

abo-abo
sumber
4

Saya akan berterus terang: Saya tidak mengerti apa artinya "gunakan untuk sintaks, bukan untuk semantik". Inilah sebabnya mengapa bagian dari jawaban ini akan membahas jawaban lunaryon , dan bagian lainnya adalah usaha saya untuk menjawab pertanyaan awal.

Ketika kata "semantik" digunakan dalam pemrograman, itu merujuk pada cara penulis bahasa memilih untuk menafsirkan bahasa mereka. Ini biasanya bermuara pada seperangkat formula yang mengubah aturan tata bahasa menjadi beberapa aturan lain yang diasumsikan pembaca sudah terbiasa. Misalnya, Plotkin dalam bukunya Structural Approach to Operational Semantics menggunakan bahasa derivasi logis untuk menjelaskan apa yang dievaluasi sintaksis abstraknya (apa artinya menjalankan program). Dengan kata lain, jika sintaks adalah yang merupakan bahasa pemrograman pada tingkat tertentu, maka itu harus memiliki beberapa semantik, jika tidak, yah ... bukan bahasa pemrograman.

Tapi, mari kita lupakan definisi formal, lagipula, penting untuk memahami maksudnya. Jadi, sepertinya lunaryon akan mendorong pembacanya untuk menggunakan makro ketika tidak ada arti baru yang ditambahkan ke program, tetapi hanya semacam singkatan. Bagi saya ini kedengarannya aneh dan sangat kontras dengan bagaimana makro sebenarnya digunakan. Di bawah ini adalah contoh-contoh yang dengan jelas menciptakan makna baru:

  1. defun dan teman-teman, yang merupakan bagian penting dari bahasa tidak dapat dijelaskan dalam istilah yang sama dengan yang Anda gambarkan panggilan fungsi.

  2. setfaturan yang menggambarkan bagaimana fungsi bekerja tidak memadai untuk menggambarkan efek dari ekspresi (setf (aref x y) z).

  3. with-output-to-stringjuga mengubah semantik kode di dalam makro. Demikian pula, with-current-bufferdan banyak with-makro lainnya .

  4. dotimes dan yang serupa tidak dapat dijelaskan dalam hal fungsi.

  5. ignore-errors mengubah semantik kode yang dibungkusnya.

  6. Ada sejumlah makro yang didefinisikan dalam cl-libpaket, eieiopaket dan beberapa perpustakaan lain yang hampir ada di mana-mana yang digunakan dalam Emacs Lisp yang, sementara mungkin terlihat seperti bentuk fungsi memiliki interpretasi yang berbeda.

Jadi, jika ada, makro adalah alat untuk memperkenalkan semantik baru ke dalam bahasa. Saya tidak bisa memikirkan cara alternatif untuk melakukan itu (setidaknya tidak di Emacs Lisp).


Ketika saya tidak akan menggunakan makro:

  1. Ketika mereka tidak memperkenalkan konstruksi baru, dengan semantik baru (yaitu fungsi akan melakukan pekerjaan sama baiknya).

  2. Saat ini hanya semacam kenyamanan (yaitu membuat nama makro mvbyang diperluas menjadi cl-multiple-value-bindhanya untuk membuatnya lebih pendek).

  3. Ketika saya berharap banyak kode penanganan kesalahan disembunyikan oleh makro (seperti telah dicatat, makro sulit untuk di-debug).


Ketika saya lebih suka makro daripada fungsi:

  1. Ketika konsep khusus domain dikaburkan oleh panggilan fungsi. Sebagai contoh, jika seseorang perlu meneruskan contextargumen yang sama ke banyak fungsi yang perlu dipanggil secara berurutan, saya lebih suka untuk membungkusnya dalam makro, di mana contextargumen itu tersembunyi.

  2. Ketika kode generik dalam bentuk fungsi terlalu bertele-tele (konstruksi iterasi kosong di Emacs Lisp terlalu bertele-tele dengan seleraku dan membuat programmer tidak perlu untuk detail implementasi tingkat rendah).


Ilustrasi

Di bawah ini adalah versi dipermudah dimaksudkan untuk memberikan satu intuisi tentang apa yang dimaksud dengan semantik dalam ilmu komputer.

Misalkan Anda memiliki bahasa pemrograman yang sangat sederhana hanya dengan aturan sintaks ini:

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

dengan bahasa ini kita dapat membuat "program" seperti (1+0)+1atau 0atau lainnya ((0+0)).

Tetapi selama kami tidak memberikan aturan semantik, "program" ini tidak ada artinya.

Misalkan kita sekarang melengkapi bahasa kita dengan aturan (semantik):

0+0  0+1  1+0  1+1  (exp)
---, ---, ---, ---, -----
 0   1+0   1    0    exp

Sekarang kita benar-benar dapat menghitung menggunakan bahasa ini. Artinya, kita dapat mengambil representasi sintaksis, memahaminya secara abstrak (dengan kata lain, mengubahnya menjadi sintaksis abstrak), kemudian menggunakan aturan semantik untuk memanipulasi representasi ini.

Ketika kita berbicara tentang keluarga Lisp bahasa, aturan semantik dasar evaluasi fungsi kira-kira adalah lambda-calculus:

(f x)  (lambda x y)   x
-----, ------------, ---
 fx        ^x.y       x

Mekanisme mengutip dan ekspansi makro juga dikenal sebagai alat meta-pemrograman, yaitu mereka memungkinkan seseorang untuk berbicara tentang program, bukan hanya pemrograman. Mereka mencapai ini dengan membuat semantik baru menggunakan "lapisan meta". Contoh makro sederhana tersebut adalah:

(a . b)
-------
(cons a b)

Ini sebenarnya bukan makro di Emacs Lisp, tetapi bisa saja. Saya memilih hanya demi kesederhanaan. Perhatikan bahwa tidak ada aturan semantik yang didefinisikan di atas yang berlaku untuk kasus ini karena kandidat terdekat (f x)mengartikannya fsebagai fungsi, sementara aitu tidak harus berfungsi.

wvxvw
sumber
1
"Gula sintaksis" sebenarnya bukan istilah dalam ilmu komputer. Ini sesuatu yang digunakan oleh para insinyur untuk memaksudkan berbagai hal. Jadi saya tidak punya jawaban yang pasti. Karena kita berbicara tentang semantik, maka aturan untuk menafsirkan sintaksis bentuk: (x y)kira-kira "mengevaluasi y, lalu panggil x dengan hasil evaluasi sebelumnya". Saat Anda menulis (defun x y), y tidak dievaluasi dan juga x. Apakah Anda dapat menulis kode dengan efek setara menggunakan semantik yang berbeda tidak menyangkal bagian kode ini memiliki semantiknya sendiri.
wvxvw
1
Komentar kedua Anda: Dapatkah Anda merujuk saya ke definisi makro yang Anda gunakan yang mengatakan apa yang Anda klaim? Saya baru saja memberi Anda contoh di mana aturan semantik baru diperkenalkan melalui makro. Setidaknya dalam arti tepatnya CS. Saya tidak berpikir bahwa berdebat tentang definisi itu produktif, dan saya juga berpikir bahwa definisi yang digunakan dalam CS adalah yang paling cocok untuk diskusi ini.
wvxvw
1
Yah ... Anda kebetulan memiliki ide sendiri tentang apa arti kata "semantik", yang agak ironis, jika Anda memikirkannya :) Tapi sungguh, hal-hal ini memiliki definisi yang sangat tepat, Anda hanya, yah .. abaikan itu. Buku yang saya tautkan dalam jawabannya adalah buku teks standar tentang semantik sebagaimana diajarkan dalam kursus terkait CS. Jika Anda baru saja membaca artikel terkait Wiki: en.wikipedia.org/wiki/Semantics_%28computer_science%29 Anda akan melihat apa itu sebenarnya. Contohnya adalah aturan untuk menafsirkan (defun x y)aplikasi fungsi vs.
wvxvw
Oh well, saya kira ini bukan cara saya berdiskusi. Adalah suatu kesalahan untuk mencoba dan memulai; Saya menghapus komentar saya karena tidak ada gunanya dalam semua ini. Saya minta maaf karena telah menyia-nyiakan waktu Anda.
lunaryorn