Membaca esai Paul Graham tentang bahasa pemrograman orang akan berpikir bahwa macro Lisp adalah satu-satunya cara untuk pergi. Sebagai pengembang yang sibuk, bekerja pada platform lain, saya belum memiliki hak istimewa untuk menggunakan macro Lisp. Sebagai seseorang yang ingin memahami buzz, tolong jelaskan apa yang membuat fitur ini begitu kuat.
Tolong juga hubungkan ini dengan sesuatu yang saya akan mengerti dari dunia pengembangan Python, Java, C # atau C.
macros
lisp
homoiconicity
minty
sumber
sumber
Jawaban:
Untuk memberikan jawaban singkat, makro digunakan untuk mendefinisikan ekstensi sintaksis bahasa ke Common Lisp atau Domain Specific Languages (DSLs). Bahasa-bahasa ini tertanam langsung ke dalam kode Lisp yang ada. Sekarang, DSL dapat memiliki sintaksis yang mirip dengan Lisp (seperti Prolog Interpreter Peter Norvig untuk Common Lisp) atau yang sama sekali berbeda (mis. Infix Notation Math for Clojure).
Berikut adalah contoh yang lebih konkret:
Python memiliki daftar pemahaman yang dibangun ke dalam bahasa. Ini memberikan sintaksis sederhana untuk kasus umum. Garis
menghasilkan daftar yang berisi semua angka genap antara 0 dan 9. Kembali di Python 1,5 hari tidak ada sintaksis seperti itu; Anda akan menggunakan sesuatu yang lebih seperti ini:
Keduanya setara secara fungsional. Mari kita memohon penangguhan ketidakpercayaan kita dan berpura-pura Lisp memiliki makro loop yang sangat terbatas yang hanya melakukan iterasi dan tidak ada cara mudah untuk melakukan yang setara dengan pemahaman daftar.
Di Lisp Anda bisa menulis yang berikut ini. Saya harus mencatat contoh yang dibuat ini dipilih untuk menjadi identik dengan kode Python bukan contoh yang baik dari kode Lisp.
Sebelum saya melangkah lebih jauh, saya harus menjelaskan apa itu makro. Ini adalah transformasi yang dilakukan pada kode dengan kode. Yaitu, sepotong kode, dibaca oleh interpreter (atau kompiler), yang mengambil kode sebagai argumen, memanipulasi dan mengembalikan hasilnya, yang kemudian dijalankan di tempat.
Tentu saja itu banyak mengetik dan programmer malas. Jadi kita bisa mendefinisikan DSL untuk melakukan pemahaman daftar. Bahkan, kami sudah menggunakan satu makro (loop makro).
Lisp mendefinisikan beberapa bentuk sintaks khusus. Kutipan (
'
) menunjukkan token berikutnya adalah literal. Kuasiquote atau backtick (`
) menunjukkan token berikutnya adalah literal dengan lolos. Lolos ditunjukkan oleh operator koma. Secara literal'(1 2 3)
adalah setara dengan Python[1, 2, 3]
. Anda dapat menetapkannya ke variabel lain atau menggunakannya di tempat. Anda dapat menganggapnya`(1 2 ,x)
sebagai yang ekivalen dengan Python di[1, 2, x]
manax
variabel yang sebelumnya didefinisikan. Notasi daftar ini adalah bagian dari keajaiban yang masuk ke makro. Bagian kedua adalah pembaca Lisp yang secara cerdas mengganti makro untuk kode tetapi yang paling baik digambarkan di bawah ini:Jadi kita bisa mendefinisikan makro yang disebut
lcomp
(kependekan dari daftar pemahaman). Sintaksnya akan persis seperti python yang kita gunakan dalam contoh[x for x in range(10) if x % 2 == 0]
-(lcomp x for x in (range 10) if (= (% x 2) 0))
Sekarang kita bisa mengeksekusi di baris perintah:
Cukup rapi, ya? Sekarang tidak berhenti di situ. Anda memiliki mekanisme, atau kuas, jika Anda mau. Anda dapat memiliki sintaks yang mungkin Anda inginkan. Seperti
with
sintaksis Python atau C # . Atau sintaks. LINQ .NET Pada akhirnya, inilah yang menarik orang ke Lisp - fleksibilitas tertinggi.sumber
(loop for x from 0 below 10 when (evenp x) collect x)
, contoh lebih lanjut di sini . Tapi memang, loop adalah "hanya makro" (Saya benar - benar menerapkannya kembali dari awal beberapa waktu lalu )Anda akan menemukan perdebatan komprehensif seputar makro makro di sini .
Subset menarik dari artikel itu:
sumber
Makro Lisp umum pada dasarnya memperluas "primitif sintaksis" dari kode Anda.
Misalnya, dalam C, konstruksi sakelar / kasus hanya berfungsi dengan tipe integral dan jika Anda ingin menggunakannya untuk pelampung atau string, Anda dibiarkan dengan pernyataan bersarang jika dan perbandingan eksplisit. Anda juga tidak bisa menulis makro C untuk melakukan pekerjaan untuk Anda.
Tapi, karena makro lisp (pada dasarnya) adalah program lisp yang mengambil potongan kode sebagai input dan mengembalikan kode untuk menggantikan "doa" makro, Anda dapat memperpanjang repertoar "primitif" sejauh yang Anda inginkan, biasanya berakhir dengan program yang lebih mudah dibaca.
Untuk melakukan hal yang sama dalam C, Anda harus menulis pra-prosesor kustom yang memakan sumber awal Anda (tidak-cukup-C) dan mengeluarkan sesuatu yang dapat dipahami oleh kompiler C. Ini bukan cara yang salah untuk melakukannya, tetapi itu belum tentu yang termudah.
sumber
Lisp macro memungkinkan Anda untuk memutuskan kapan (jika ada) bagian atau ekspresi apa pun akan dievaluasi. Untuk memberikan contoh sederhana, pikirkan C:
Apa yang dikatakannya adalah: Evaluasi
expr1
, dan, jika benar, evaluasiexpr2
, dll.Sekarang coba buat ini
&&
menjadi fungsi ... itu benar, Anda tidak bisa. Memanggil sesuatu seperti:Akan mengevaluasi ketiganya
exprs
sebelum memberikan jawaban terlepas dari apakahexpr1
itu salah!Dengan macro lisp Anda dapat mengkodekan sesuatu seperti:
sekarang Anda memiliki
&&
, yang dapat Anda panggil seperti fungsi dan tidak akan mengevaluasi bentuk apa pun yang Anda berikan kepadanya kecuali semuanya benar.Untuk melihat manfaatnya, kontras:
dan:
Hal lain yang dapat Anda lakukan dengan makro adalah membuat kata kunci baru dan / atau bahasa mini (lihat
(loop ...)
makro untuk contoh), mengintegrasikan bahasa lain ke dalam lisp, misalnya, Anda bisa menulis makro yang memungkinkan Anda mengatakan sesuatu seperti:Dan itu bahkan tidak masuk ke makro Reader .
Semoga ini membantu.
sumber
(and ...
akan mengevaluasi ekspresi sampai seseorang mengevaluasi ke false, perhatikan bahwa efek samping yang dihasilkan oleh evaluasi palsu akan terjadi, hanya ekspresi berikutnya yang akan dilewati.Saya tidak berpikir saya pernah melihat Lisp macro dijelaskan lebih baik daripada oleh orang ini: http://www.defmacro.org/ramblings/lisp.html
sumber
Pikirkan apa yang dapat Anda lakukan di C atau C ++ dengan makro dan template. Mereka adalah alat yang sangat berguna untuk mengelola kode berulang, tetapi mereka terbatas dalam cara yang cukup parah.
Lisp dan Lisp macro menyelesaikan masalah ini.
Bicaralah dengan siapa pun yang menguasai C ++ dan tanyakan kepada mereka berapa lama mereka menghabiskan mempelajari semua templat yang mereka butuhkan untuk melakukan metaprogramming templat. Atau semua trik gila dalam buku-buku (luar biasa) seperti Modern C ++ Design , yang masih sulit untuk di-debug dan (dalam praktiknya) non-portable antara kompiler dunia nyata meskipun bahasa tersebut telah distandarisasi selama satu dekade. Semua itu mencair jika bahasa yang Anda gunakan untuk metaprogramming adalah bahasa yang sama yang Anda gunakan untuk pemrograman!
sumber
Makro lisp mengambil fragmen program sebagai input. Fragmen program ini mewakili struktur data yang dapat dimanipulasi dan diubah dengan cara apa pun yang Anda suka. Pada akhirnya makro menghasilkan fragmen program lain, dan fragmen ini adalah apa yang dieksekusi saat runtime.
C # tidak memiliki fasilitas makro, akan tetapi setara jika kompilator mengurai kode menjadi pohon CodeDOM, dan meneruskannya ke metode, yang mengubahnya menjadi CodeDOM lain, yang kemudian dikompilasi menjadi IL.
Ini dapat digunakan untuk mengimplementasikan sintaksis "gula" seperti
for each
-kurang-using
klausa, linq -ekspresiselect
dan sebagainya, sebagai makro yang mentransformasikannya menjadi kode yang mendasarinya.Jika Java memiliki makro, Anda bisa mengimplementasikan sintaks Linq di Java, tanpa perlu Sun untuk mengubah bahasa dasar.
Berikut ini adalah pseudo-code untuk tampilan makro gaya lisp di C # untuk implementasi
using
:sumber
using
akan terlihat seperti ini ;)Karena jawaban yang ada memberikan contoh konkret yang baik menjelaskan apa yang dicapai makro dan bagaimana, mungkin akan membantu untuk mengumpulkan bersama beberapa pemikiran tentang mengapa fasilitas makro adalah keuntungan yang signifikan dalam kaitannya dengan bahasa lain ; pertama dari jawaban ini, lalu yang besar dari tempat lain:
- Vatine
- Matt Curtis
- Miguel Ping
- Peter Seibel, dalam "Praktis Masalah Umum"
sumber
Saya tidak yakin dapat menambahkan beberapa wawasan ke posting semua orang (luar biasa), tetapi ...
Makro Lisp berfungsi dengan baik karena sifat sintaks Lisp.
Lisp adalah bahasa yang sangat teratur (anggap semuanya adalah daftar ); makro memungkinkan Anda untuk memperlakukan data dan kode sebagai sama (tidak diperlukan penguraian string atau peretasan lainnya untuk memodifikasi ekspresi lisp). Anda menggabungkan kedua fitur ini dan Anda memiliki cara yang sangat bersih untuk memodifikasi kode.
Sunting: Apa yang saya coba katakan adalah bahwa Lisp adalah homoikonik , yang berarti bahwa struktur data untuk program lisp ditulis dalam lisp itu sendiri.
Jadi, Anda berakhir dengan cara membuat generator kode Anda sendiri di atas bahasa menggunakan bahasa itu sendiri dengan semua kekuatannya (misalnya di Jawa Anda harus meretas jalan Anda dengan tenun bytecode, meskipun beberapa kerangka kerja seperti AspectJ memungkinkan Anda untuk lakukan ini dengan menggunakan pendekatan yang berbeda, ini pada dasarnya hack).
Dalam praktiknya, dengan makro Anda akhirnya membangun bahasa mini Anda sendiri di atas lisp, tanpa perlu belajar bahasa atau alat tambahan, dan dengan menggunakan kekuatan penuh dari bahasa itu sendiri.
sumber
S-expressions
, bukan daftar.Macro Lisp mewakili pola yang terjadi di hampir semua proyek pemrograman yang cukup besar. Akhirnya dalam program besar Anda memiliki bagian kode tertentu di mana Anda menyadari itu akan lebih sederhana dan lebih sedikit kesalahan cenderung bagi Anda untuk menulis sebuah program yang menampilkan kode sumber sebagai teks yang kemudian bisa Anda tempelkan.
Dalam objek Python memiliki dua metode
__repr__
dan__str__
.__str__
hanyalah representasi yang dapat dibaca manusia.__repr__
mengembalikan representasi yang merupakan kode Python yang valid, artinya, sesuatu yang dapat dimasukkan ke dalam interpreter sebagai Python yang valid. Dengan cara ini Anda dapat membuat potongan kecil Python yang menghasilkan kode yang valid yang dapat ditempelkan ke sumber Anda yang sebenarnya.Dalam Lisp seluruh proses ini telah diformalkan oleh sistem makro. Tentu itu memungkinkan Anda untuk membuat ekstensi ke sintaks dan melakukan segala macam hal mewah, tetapi kegunaan sebenarnya diringkas oleh di atas. Tentu saja itu membantu bahwa sistem makro Lisp memungkinkan Anda untuk memanipulasi "cuplikan" ini dengan kekuatan penuh dari seluruh bahasa.
sumber
Singkatnya, makro adalah transformasi kode. Mereka memungkinkan untuk memperkenalkan banyak konstruksi sintaksis baru. Misalnya, pertimbangkan LINQ dalam C #. Di lisp, ada ekstensi bahasa yang serupa yang diterapkan oleh makro (misalnya, built-in loop construct, iterate). Makro secara signifikan mengurangi duplikasi kode. Macro memungkinkan menanamkan «sedikit bahasa» (misalnya, di mana di c # / java satu akan menggunakan xml untuk mengkonfigurasi, dalam lisp hal yang sama dapat dicapai dengan makro). Macro mungkin menyembunyikan kesulitan menggunakan penggunaan perpustakaan.
Misal, di lisp Anda bisa menulis
dan ini menyembunyikan semua basis data (transaksi, penutupan koneksi yang tepat, mengambil data, dll.) sedangkan di C # ini membutuhkan pembuatan SqlConnections, SqlCommands, menambahkan SqlParameters ke SqlCommands, mengulang pada SqlDataReaders, menutupnya dengan benar.
sumber
Sementara yang di atas menjelaskan makro apa dan bahkan memiliki contoh keren, saya pikir perbedaan utama antara fungsi makro dan normal adalah bahwa LISP mengevaluasi semua parameter terlebih dahulu sebelum memanggil fungsi. Dengan makro sebaliknya, LISP meneruskan parameter yang tidak dievaluasi ke makro. Misalnya, jika Anda meneruskan (+ 1 2) ke suatu fungsi, fungsi tersebut akan menerima nilai 3. Jika Anda meneruskan ini ke makro, itu akan menerima Daftar (+ 1 2). Ini dapat digunakan untuk melakukan semua jenis hal yang sangat berguna.
Ukur waktu yang diperlukan untuk menjalankan fungsi yang dilewati. Dengan fungsi, parameter akan dievaluasi sebelum kontrol dilewatkan ke fungsi. Dengan makro, Anda dapat memisahkan kode Anda antara mulai dan berhenti dari stopwatch Anda. Di bawah ini memiliki kode yang sama persis di makro dan fungsi dan output sangat berbeda. Catatan: Ini adalah contoh yang dibuat-buat dan implementasinya dipilih sehingga identik untuk menyoroti perbedaan dengan lebih baik.
sumber
Saya mendapatkan ini dari buku resep The Common Lisp tapi saya pikir itu menjelaskan mengapa macro lisp baik dalam cara yang baik.
"Makro adalah bagian biasa dari kode Lisp yang beroperasi pada sepotong kode Lisp putatif lainnya, menerjemahkannya menjadi (versi lebih dekat dengan) Lisp yang dapat dieksekusi. Itu mungkin terdengar agak rumit, jadi mari kita berikan contoh sederhana. Misalkan Anda ingin sebuah versi setq yang menetapkan dua variabel ke nilai yang sama. Jadi jika Anda menulis
ketika
z=8
kedua x dan y diatur ke 11. (Saya tidak bisa memikirkan kegunaan untuk ini, tapi itu hanya sebuah contoh.)Seharusnya jelas bahwa kita tidak dapat mendefinisikan setq2 sebagai fungsi. Jika
x=50
dany=-5
, fungsi ini akan menerima nilai 50, -5, dan 11; itu tidak akan memiliki pengetahuan tentang variabel apa yang seharusnya ditetapkan. Apa yang benar-benar ingin kami katakan adalah, Ketika Anda (sistem Lisp) melihatnya(setq2 v1 v2 e)
, perlakukan itu setara dengan(progn (setq v1 e) (setq v2 e))
. Sebenarnya, ini tidak benar, tetapi akan dilakukan untuk saat ini. Makro memungkinkan kita untuk melakukan ini dengan tepat, dengan menentukan program untuk mengubah pola input(setq2 v1 v2 e)
"menjadi pola output(progn ...)
."Jika Anda pikir ini bagus, Anda dapat terus membaca di sini: http://cl-cookbook.sourceforge.net/macros.html
sumber
setq2
sebagai fungsi jikax
dany
dilewatkan dengan referensi. Saya tidak tahu apakah itu mungkin di CL, namun. Jadi bagi seseorang yang tidak tahu Lisps atau CL khususnya ini bukan contoh yang sangat ilustratif IMODalam python Anda memiliki dekorator, Anda pada dasarnya memiliki fungsi yang mengambil fungsi lain sebagai input. Anda dapat melakukan apa yang Anda inginkan: panggil fungsi, lakukan sesuatu yang lain, bungkus panggilan fungsi dalam rilis perolehan sumber daya, dll. Tetapi Anda tidak bisa mengintip ke dalam fungsi itu. Katakanlah kami ingin membuatnya lebih kuat, misalkan dekorator Anda menerima kode fungsi sebagai daftar maka Anda tidak hanya dapat menjalankan fungsinya, tetapi kini Anda dapat menjalankan bagiannya, menyusun ulang garis fungsi, dll.
sumber