Pendapat berbeda: Homoniklikat Lisp jauh lebih tidak berguna daripada yang Anda yakini.
Untuk memahami makro sintaksis, penting untuk memahami kompiler. Tugas kompiler adalah mengubah kode yang bisa dibaca manusia menjadi kode yang dapat dieksekusi. Dari perspektif tingkat yang sangat tinggi, ini memiliki dua fase keseluruhan: parsing dan pembuatan kode .
Parsing adalah proses membaca kode, menafsirkannya sesuai dengan seperangkat aturan formal, dan mengubahnya menjadi struktur pohon, umumnya dikenal sebagai AST (Abstract Syntax Tree). Untuk semua keragaman di antara bahasa pemrograman, ini adalah satu kesamaan yang luar biasa: pada dasarnya setiap bahasa pemrograman tujuan umum diurai menjadi struktur pohon.
Pembuatan kode mengambil AST parser sebagai inputnya, dan mengubahnya menjadi kode yang dapat dieksekusi melalui penerapan aturan formal. Dari perspektif kinerja, ini adalah tugas yang jauh lebih sederhana; banyak kompiler bahasa tingkat tinggi menghabiskan 75% atau lebih dari waktu mereka untuk parsing.
Yang perlu diingat tentang Lisp adalah bahwa itu sangat, sangat tua. Di antara bahasa pemrograman, hanya FORTRAN yang lebih tua dari Lisp. Jauh di masa lalu, penguraian (bagian kompilasi yang lambat) dianggap sebagai seni yang gelap dan misterius. Makalah asli John McCarthy tentang teori Lisp (ketika itu hanya sebuah ide yang dia tidak pernah berpikir dapat benar-benar diterapkan sebagai bahasa pemrograman komputer nyata) menggambarkan sintaksis yang agak lebih kompleks dan ekspresif daripada "ekspresi-S" modern di mana-mana untuk semuanya. "notasi. Itu terjadi kemudian, ketika orang mencoba untuk benar-benar mengimplementasikannya. Karena parsing tidak dipahami dengan baik pada saat itu, mereka pada dasarnya menyadapnya dan membodohi sintaksis ke dalam struktur pohon homoikonik untuk membuat pekerjaan parser sama sekali sepele. Hasil akhirnya adalah bahwa Anda (pengembang) harus melakukan banyak pengurai Bekerja untuk itu dengan menulis AST formal langsung ke kode Anda. Homoiconicity tidak "membuat makro jadi lebih mudah" sebanyak itu membuat menulis segala sesuatu yang jauh lebih sulit!
Masalah dengan ini adalah, terutama dengan pengetikan dinamis, sangat sulit bagi ekspresi-S untuk membawa banyak informasi semantik bersamanya. Ketika semua sintaks Anda adalah jenis yang sama (daftar daftar), tidak ada banyak cara konteks yang disediakan oleh sintaks, dan sistem makro memiliki sangat sedikit untuk dikerjakan.
Teori kompiler telah berjalan jauh sejak 1960-an ketika Lisp ditemukan, dan sementara hal-hal yang dikerjakannya mengesankan untuk zamannya, mereka terlihat agak primitif sekarang. Untuk contoh sistem pemrograman metamodern, lihatlah bahasa Boo (yang sayangnya kurang dihargai). Boo diketik secara statis, berorientasi objek, dan open-source, sehingga setiap node AST memiliki tipe dengan struktur yang terdefinisi dengan baik yang dapat dibaca oleh pengembang makro. Bahasa ini memiliki sintaksis yang relatif sederhana yang terinspirasi oleh Python, dengan berbagai kata kunci yang memberikan makna semantik intrinsik pada struktur pohon yang dibangun darinya, dan metaprogramming-nya memiliki sintaks kuasiquote yang intuitif untuk menyederhanakan pembuatan node AST baru.
Inilah makro yang saya buat kemarin ketika saya menyadari saya menerapkan pola yang sama ke banyak tempat yang berbeda dalam kode GUI, di mana saya akan memanggil BeginUpdate()
kontrol UI, melakukan pembaruan di try
blok, dan kemudian memanggil EndUpdate()
:
macro UIUpdate(value as Expression):
return [|
$value.BeginUpdate()
try:
$(UIUpdate.Body)
ensure:
$value.EndUpdate()
|]
The macro
perintah ini, pada kenyataannya, makro itu sendiri , yang mengambil tubuh makro sebagai masukan dan menghasilkan kelas untuk memproses makro. Itu menggunakan nama makro sebagai variabel yang berdiri untuk MacroStatement
node AST yang mewakili permintaan makro. [| ... |] adalah blok kuasiquote, menghasilkan AST yang sesuai dengan kode di dalamnya, dan di dalam blok ququote, simbol $ menyediakan fasilitas "unquote", menggantikan dalam node seperti yang ditentukan.
Dengan ini, dimungkinkan untuk menulis:
UIUpdate myComboBox:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
dan mengembangkannya ke:
myComboBox.BeginUpdate()
try:
LoadDataInto(myComboBox)
myComboBox.SelectedIndex = 0
ensure:
myComboBox.EndUpdate()
Mengekspresikan makro dengan cara ini lebih sederhana dan lebih intuitif daripada dalam makro Lisp, karena pengembang tahu struktur MacroStatement
dan tahu cara kerja Arguments
dan Body
properti, dan bahwa pengetahuan yang melekat dapat digunakan untuk mengekspresikan konsep yang terlibat dalam sangat intuitif cara. Ini juga lebih aman, karena kompiler mengetahui strukturnya MacroStatement
, dan jika Anda mencoba kode sesuatu yang tidak valid untuk MacroStatement
, kompiler akan segera menangkapnya dan melaporkan kesalahan alih-alih Anda tidak tahu sampai sesuatu meledak pada Anda di runtime.
Mencangkokkan makro ke Haskell, Python, Java, Scala, dll. Tidak sulit karena bahasa ini bukan homoikonik; sulit karena bahasa tidak dirancang untuk mereka, dan itu berfungsi dengan baik ketika hierarki AST bahasa Anda dirancang dari bawah ke atas untuk diperiksa dan dimanipulasi oleh sistem makro. Saat Anda bekerja dengan bahasa yang dirancang dengan metaprogramming dari awal, makro jauh lebih sederhana dan lebih mudah untuk dikerjakan!