Python ile metaprogramlamaya bir bakış
Haddim olmasa da bu konu hakkında bir yazı yazmak istiyorum zira bu konu hakkında Türkçe kaynakların bolluğundan yakınmıyoruz; aynı zamanda yazmak kişisel öğrenmemin de bir parçası.
Önce tanımlamak gerekirse, metaprogramlama (programlama üstü ya da üst programlama) adından da anlaşılacağı üzerinde program programlamayı bir problem olarak inceliyor.
Bir başka deyişle, normal işlerimizde bir program yazarız ve bu program bir “problem”i çözer. Eğer çözdüğünüz “problem” programlama ile ilgili ise metaprogramlama yapıyorsunuz demektir.
Metaprogramlama yapılarını herkes kullanabilir ama ekseriyetle SDK/API ve kütüphane tasarımcılarının işine yarıyor zira kullanıcılara çok fazla kod yazdırmadan zengin bir işlevsellik sunmuş oluyorlar.
Decorator’lar
Neredeyse her Python programcısı eninde sonunda bu yapıyı kullanır, o yüzden bu yapının nasıl çalıştığı konusunda bir içgörü edinmek oldukça önemli. Eğer decorator nedir bilmiyorsanız, ya da kafanızda canlanmadıysa şu örneğe bakabilirsiniz:
@app.route("/")
def home():
return render_template("home.html")
Bu örnek Flask’dan, app.route
isimli bir decorator, home
isimli bir
fonksiyona eklenmiş. Sadece bu basit eklentiyle, sıradan bir fonksiyon
controller’e çevrilmiş oluyor, işte fazla kod yazdırmadan işlevsellik
kazandırmanın en basit örneklerinden biri.
Peki decorator’lar nedir? Biliyorsunuz ki Python’da her şey bir nesne,
mesela 1.5 + 2.5
yazmak yerine float
nesnesinin metodunu
1.5.__add__(2.5)
şeklinde çağırabiliyoruz.
Bazı nesneler var ki bunların çağrılabilme özellikleri var, örneğin
fonksiyonlar veya metotlar; bunları çağırmak için de ()
kullanıyoruz. int
de bir nesne bildiğiniz gibi, eğer bu nesneyi çağırırsanız size 0 döner
(bir argüman verirseniz de o argümanı int’e cast eder/çevirir ve onu döner).
Fakat 5
‘i düşünelim, bu da bir nesne ve tipi int
ama bu nesne
çağrılabilir değil:
>>> int()
0
>>> 5()
<stdin>:1: SyntaxWarning: 'int' object is not callable; perhaps you missed a comma?
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
Bu bilgilerle birlikte, en ilkel tabiriyle, bir decorator argüman olarak bir çağrılabilir alan bir çağrılabilendir.
Hatta çoğu decorator çağrılabilen döner.
“Yahu Şuayip, sen int() deyince, aslında
int.__init__
ya daint.__new__
çalışıyor, yani sen çağırma yapmıyorsun, aslında class initialize ediyorsun!” diyebilirsiniz. Çağırabilme kavramı biraz daha karmaşık olabilir, o yüzden bir C implementation’a bakın, emin olmak için Python’da builtincallable()
ile bir nesenin çağırabilir olup olmadığını öğrenebilirsiniz.
Çağrılabilme kavramını anladıysanız oldukça basit bir tanımı ve yapısı var decorator’ların. Şimdi örnek bir decorator görelim ve açıklayalım:
def deco(func):
print("Decorated %s" % func)
return func
Bu örnek decorator, decorate ettiği çağrılabilir nesneyi her decorate ettiğimizde decorate ettiğimiz şeyi yazdırıyor. Tabii bu pek yararlı değil zira genelde biz nesnenin çağırdığı senaryodaki işlevselliğini değiştirmek isteriz.
Yine de bazen programın ilk çalışma anında decorate edilen nesnelerin davranışını değiştirmek için bu yapıyı kullanabiliriz. Mesela bir class’i decorate ettiğimizi düşünelim, bu yapıyı kullanarak class’ın kendisine ait bazı metotları ve attribute’ları değiştirebiliriz. Ya da daha uçuk kaçık bir şey yapıp, decorate ettiğimiz class’i döndürmek yerine başka bir class veya nesne döndürebiliriz (bunu bir nevi sonraki adımda yapıyoruz).
Şimdi nesnelerin çağırılma anına etki eden bir decorator yazalım:
def deco(func):
def inner(*args, **kwargs):
print("Calling %s" % func)
return func(*args, **kwargs)
return inner
Gördüğünüz üzere çağrılma anına etki etmek için bir fonksiyon tanımı yapmamız gerekiyor. İşin aslında, herhangi bir nesne alıp yeni, alakasız bir function nesnesi geri döndürüyoruz. Bu yeni function nesnesi de scope’unda bulunan, yani decorate edilmiş nesneyi çağırıyor, böylelikle orijinal nesneyi taklit ediyor.
Bu yapıyı, fonksiyon bağlamında düşünürsek, fonksiyonu her çağırdığımızda ek olarak bir şeyler yazdırıyoruz. Bu senaryo genelde log amaçlarıyla kullanılabiliyor; mesela fonksiyonlar ne zaman çağrılmış kaydı tutulsun diye.
Şimdi decoratorlar’ların nasıl uygulandığı konusunda ufak bir parantez açalım. Yani decorator yazdık iyi tamam, peki bir fonksiyonu nasıl decorate edeceğiz? Şimdi örneği görelim:
def echo(number):
return number
echo = deco(echo)
Decorator’lar bahsettiğimiz üzere çağrılabilir alan çağrılabilen olduklarından, decorate etmek istediğimiz nesneyi argüman olarak decorator’a vermemiz yeterli. Tabii bu sırada decorate ettiğimiz nesneyi yeniden tanımlamamız gerekiyor (ki aynı isimle kullanabilelim).
Decorator’ların uygulanma prensibinin temelde bu olduğunu anlamak önemli zira sıradaki göstereceğim syntax’i direk yutarsanız ne yaptığımızı anlamanız zorlaşabilir. İşte decorator’ları uygulamanın bir başka yolu:
@deco
def echo(number):
return number
Bu syntax tamamen işimizi kolaylaştırmak, sürekli tekrar eden kod yazmamak için Python tarafından bize sunuluyor; çoğunlukla da decorator’lar böyle uygulanır. Bu syntax’in temelde yaptığı şeyin yukarıdaki ile aynı olduğunu tekrar hatırlatmak istiyorum.
Şimdi şu koda bir bakalım:
def deco0(func):
print("Decorating %s" % func)
return func
def deco(func):
def inner(*args, **kwargs):
print("Calling %s" % func)
return func(*args, **kwargs)
return inner
def echo(number):
return number
echo_1 = deco(echo)
echo_2 = deco0(echo)
echo_1 is echo # output: False
echo_2 is echo # output: True
Yukarıda yine örnekler üzerinden 2 tane decorator tanımladım, bir fonksiyonu da bu ikisi decorator ile decorate ettim. deco0’da direkt nesnenin kendisini, deco’da ise yeni oluşturduğum fonksiyonu dönüyorum.
Son iki satırdaki statement’lerin çıktılarını incelersek, yeni fonksiyon yani yeni bir nesne dönen decorator’un decorate ettiği fonksiyonu değiştirdiğini görüyoruz. Decorate ettiğimiz fonksiyon çağrıldığında yine eski nesnenin davranışı taklit edileceği için bu durumda sorun olmayacaktır, yani girdiğimiz sayı aynen bize dönecektir, beklediğimiz gibi.
Peki ya çağrılmadan önceki durum nedir? Decorate edilmiş nesne kendisi gibi
değil, içerde döndüğümüz inner fonksiyonu gibi davranıyor! Bunu tabii ki
istemeyiz. Bunun yol açtığı bazı sıkıntılar var, mesela decorate ettiğiniz
function’a docstring koymuşsanız bu kaybolur. Ya da daha dramatik olarak, inner
metodu orijinal nesnenin imzasıyla aynı değilse metodun imzasını kaybedersiniz.
Örneğin bu bağlamda decorate edilmiş fonksiyonu echo(254, domates=123)
şeklinde çağırmanızda problem olmaz (ancak bu argümanlar orijinal metoda
gidince hata çıkar, o sürece kadar giden tüm kodlar gereksiz çalışmış olur).
Bu durumu düzeltmek için bir aracımız var, ne tesadüf ki bu araç da bir decorator. Şimdi bu decorator’un düzgün haline bakalım:
import functools
def deco(func):
@functools.wraps(func)
def inner(*args, **kwargs):
print("Calling %s" % func)
return func(*args, **kwargs)
return inner
yani:
def deco(func):
def inner(*args, **kwargs):
print("Calling %s" % func)
return func(*args, **kwargs)
return functools.wraps(func)(inner)
functools.wraps
decorate ettiği fonksiyonu, argüman olarak veren fonksiyona
benzetmeye yarayan bir araç. Burada benzetme kelimesi önemli, zira decorate
ettiğiniz nesne her zaman değişiyor; önemli olan bu nesnenin davranışının
olabildiğince aynı kalması. wraps, yukarıda bahsettiğim problemleri ve
birkaçını daha çözüyor; bu metodu nesneyi değiştiren her decorator yazdığımızda
kullanmalıyız.
Daha önceden decorator’ların çağrılabilen alan bir çağrılabilir olduğunu söylemiştim. Dikkat ederseniz, wraps daha tanımında çağrılabilen alıyor, üzerine de bir function decorate ediyor. O zaman wraps decorator döndüren bir decorator mü oluyor? Muhtemelen hayır; bunlara argüman alabilen decorator’lar diyoruz. Ama “decorator döndüren decorator” gayet geçerli ve istek ve eminim ki örnekleri vardır.
Bu aşamada argüman alabilen decorator nasıl yazılır’ı anlatmayacağım, ama “decorator alan decorator” düşüncesi size fikir vermiş olmalı. Hemen küçük bir parantezde, argüman alan decorator yazmayı kolaylaştıran kütüphanemin reklamını yapmak isterim; bunu hem de class yazarak yapıyorsunuz!
Çağırma anına etki eden bir decorator’un, illa ki function dönmesine gerek yok, herhangi bir çağrılabilir nesne dönebilir. Fakat wraps function dışındaki nesneler için düzgün çalışmıyor. O yüzden genelde function dönülmesi hayırlı olur. Bu neyi decorate ettiğinize göre değişir tabii.
type
Eğer bir nesnenin type’ini merak edersek bunu tek argüman olarak bu arkadaşa verebiliriz; bunu hepimiz biliyoruz.
Daha az bilinen şey ise type, 3 argüman aldığı zaman yeni bir ‘type’ oluşturur. Peki ‘type’ nedir? Önce bir class tanımı yapalım:
class Person:
def __init__(self, name):
self.name = name
ismet = Person(name="İsmet")
Bu bağlamda type(ismet)
ne diye bakarsanız Person
olduğunu görürsünüz. Peki
bir de type(Person)
neymiş diye bakarsak onun da type
olduğunu görüyoruz.
Sonuç olarak bir bir class tanımı yaptığımız zaman Python gidip bize özel bir
type nesnesi oluşturuyor (bu bağlamda Person
). Python’da her şey bir nesne
demiştik; class’lar de bir nesnedir ve type
‘ın subclass’ı olurlar.
Oysa type’in type’ina bakarsaniz yine type karşınıza çıkar. Demek ki gökküşağının sonunda type var.
Bir type oluşturulduğunda birkaç karakteristik özelliği olur:
- İsmi ne?
- Hangi base classları almış, yani neyi inherit etmiş?
- Class attribute’leri neler, yani class gövdesinde hangi tanımlar yapılmış?
Ancak bu üç bilgi elimizde ise, bir type oluşturabiliriz. Peki nasıl ya da ne kullanarak type oluşturabiliriz? Metaclass kullanarak.
Bir class’ın davranışını belirleyen classlara ‘metaclass’ denir; bu davranışlardan biri de oluşturulma şeklidir. Eğer bir metaclass belirtmezseniz, oluşturduğunuz tüm type’lar ‘type’ metaclass’i ile oluşturulur, yani şu şekilde:
def __init__(self, name):
self.name = name
Person = type("Person", (), {"__init__": __init__})
ismet = Person(name="İsmet")
Buradaki argüman’lar yukarıda belirttiğim karakteristik özelliklere denk geliyorlar. Sonuç olarak anlamamız gereken şunlar:
- Her class aslında type’ın bir subtype’ıdır.
- Type’ları oluşturmanın tek yolu, class syntax’ı değil.
- Type’ların oluşturulmasını (ve diğer davranışlarını) değiştiren veya kontrol eden özel type’lar var.
Metaclass kavramı
Metaclass’ların ne olduğunu yukarıda öğrenmiştik. Şimdi kendimizi metaclass yazmak için motive etmeye çalışalım. Mesela bir class’ın oluşum sürecine neden müdahale etmek isteyelim ki?
Bunun üzerine çokça düşünürseniz, bir sebep bulamayacaksınız zira metaclass kullanımı günlük programlama rutinimiz için çok ileri bir çözüm. Genel tavsiye de, kodun karmaşıklığını çoğaltmamak için metaclass yazılmaması yönünde.
Şimdi sizin için bir metaclass kullanma bahanesi üreteceğim. Diyelim ki yukarıda öğrendiğimiz decorator bilgileriyle çok güzel bir class decorator’u yazdık (bu decorator’un ne yaptığı önemli değil). Fakat bu decorator’u gidip 50 tane class’in üstüne decorate etmemiz gerektiğini fark ettik, tabii canımız çok sıkıldı; çok fazla kod tekrarı yapacağız. İleride bu decorator’un bir argümanınını değiştirmek istersek vay halimize!
Metaclass bu bağlamda bir çözüm sağlayabilir. Bu classların base classına özel bir metaclass uydurup, classın oluşturulma aşamasında class’ı decorate edebiliriz. Metaclass subclasslara propagete ettiği için tüm subclasslar da decorate olmuş olur. Şimdi örnek üzerinden inceleyelim:
class PersonMeta(type):
def __new__(mcs, name, bases, classdict):
cls = super().__new__(mcs, name, bases, classdict)
cls = super_useful_decorator(cls)
return cls
class Person(metaclass=PersonMeta):
def __init__(self, name):
self.name = name
Şimdi buradaki anahtar gözlemleri sıralayalım:
- Metaclass type’in subclass’ı, zira type da bir metaclass. Metaclass işlevselliği için bu subtyping’a ihtiyacımız var (zira arkada C ile dönen bir implementation var; oraya kadar inemiyoruz).
- Buradaki
__new__
sıradan__new__
ile aynı imzaya sahip değil, normalde__new__
,__init__
’den önce çalışır ve instance’yi oluşturmakla görevlidir. Oysa burda class’ın kendisini oluşturmakla görevli. __new__
’in imzasıtype()
’nın 3 argüman alan imzasıyla aynı, yani yukarıda bahsettiğimiz üç karakteristik.- Bir class’ın metaclass’ını belirtmek için, class tanımında metaclass keyword argümanı kullanılır.
__new__
içindeki oluşturma mekanizması için yine type’a başvuruyoruz (super yoluyla; burada açık açıkcls = type(name, bases, classdict)
da diyebilirdik, ama yaygın kullanım super’i çağırmaktır). Demek ki type oluşumuna müdahele etmek aslında o kadar karmaşık bir işlem değil. Ya bu üç karakteristiği değiştireceğiz ya da yeni oluşturulan type nesnesi üzerinde birtakım işlemler yapacağız.
Yani görünen o ki yukarıdaki verdiğim basit template ile __new__
gövdesinde
birtakım özelleştirmeler ile type oluşumunu değiştiren bir metaclass
yazabiliyoruz. Tabii sadece __new__
metodunu değiştirmek zorunda değilsiniz,
ama en çok değiştirilmek istenen metot bu. Diğer metotların çalışma
mekanizmaları için örn. __call__
daha incelikli detaylar var, bunları
kullanmadan önce araştırmalısınız.
Tabii Python developer’ler de metaclass’ların pek karmaşık yapılalar
olduklarını anlamışlar ki bunların üzerine bazı soyutlama katmanları yapmışlar.
Bunlardan iki tanesi şunlar: şimdi bahsedeceğim __init_subclass__
ve daha
sonra descriptor kısmında bahsedeceğim __set_name__
metotları.
Metaclass’a başvurmanın en sık sebebi bir ‘kayıt etme’ işlevselliği oluşturmak. Django üzerinden bir örnek vereyim. Django’da model kavramı var ve bunlar özetle class tanımlarını database tablolarına çeviriyor. Bu aşamada Django “acaba hangi class’lar model?” diye merak ediyor zira database tarafında tablolarını oluşturacak. Bu yüzden her Model classını inherit alan bir class oluşturduğumuzda Django bunu kayıt edecek bir mantığa ihtiyaç duyuyor.
Bu aşamada demin aynı decorator’lar için uyguladığımız metodu uygulayıp, bu sefer decorate etmek yerine classları bir listeye atan bir logic yazabilirdi. Bu sayede Model class’ini subtype etmiş tüm classların listesi elimizde olurdu.
__init_subclass__
metodu da tanımlandığı class’tan başka bir class
inherit edince çalışan bir metot ve yukarıdaki senaryo için bire bir. Bir
örnek vermem gerekirse:
registry = []
class Person:
def __init__(self, name):
self.name = name
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
registry.append(cls)
Bu Person class’inden inherit eden herhangi bir class oluşturduğumuz zaman, registry’nin dolduğunu göreceksiniz. Aynı zamanda bu metot için gelişigüzel keyword argümanları da tanımayabilirsiniz, bu argümanlar class’ın tanımında verilebiliyor, mesela:
class Person:
def __init__(self, name):
self.name = name
def __init_subclass__(cls, location, **kwargs):
super().__init_subclass__(**kwargs)
cls.location = location
registry.append(cls)
class RichPerson(Person, location="Los Angeles"):
pass
Tabii bu özelliği metaclass __new__
de sağlıyor (classdict sonrası keyword
argument ekleme yoluyla). Aynı zamanda __init_subclass__
kullanmak için
herhangi bir metaclass tanımı yapmadığımıza dikkat edin.
Son olarak, metaclass yazarken farkında olmanız gereken bir şey, yazdığınız metaclass davranışını değiştireceği class’dan bağımsız, kendine münhasır bir class. Yani metaclass’ın gövdesine bir metot koyayım ve oluşturduğum class’lar bu metodu çağırabilsin diye bir mantık yok; ki böyle bir mantığa gerek de yok, direkt oluşturduğunuz class’a metot enjekte edebilirsiniz (ya da düz inheritance kullanabilirsiniz). Bunu standart inheritance’ye alıştıysanız kafanızı karıştırabilir diye özellikle söylüyorum.
Descriptor’lar
Descriptor’lar kısaca class’ların attribute erişimini özelleştirmemize yarıyor, bir başka tabirle “nokta operatörünü özelleştirmek”. Mesela şu örnek üzerinden gidelim:
class Person:
name = None
person = Person()
person.name = "İsmet"
Bu örnek güzel ama mesela şunları yapmak istesek nasıl yapardık acaba?
- Eğer Person’a name atanacak ise, bu name en az 3 karakter olsun, değilse hata versin.
- Eğer Person’un name’si belirlenmemiş ise, person.name diye çağırdığımız zaman “isim belirilmemiş” diye hata versin.
- Person’a name atanınca Person’un eski isimleri bir listede tutulsun ve person.old_names şeklinde erişebilelim.
Şimdi biraz beyin jimnastiği yaparsanız ve kendinizi çok kasarsanız bunların işlevselliğin hepsini oldukça karmaşık metaclass yapısıyla bir şekilde halledebileceğimizin farkına varabilirsiniz. Tabii bunu istemeyiz, bunun yerine descriptor’un sağladığı soyutlaştırmayı kullanabiliriz. Şimdi yukarıdaki işlevselliğin kazandırıldığı bir örnek görelim:
class Name:
names = []
current_name = None
def __set__(self, instance, value):
if len(value) < 3:
raise ValueError(
"Please provide a name that" " is at least 3 characters."
)
self.names.append(value)
instance.old_names = self.names[:-1]
self.current_name = value
def __get__(self, instance, owner):
if self.current_name is None:
raise ValueError("Name is not set.")
return self.current_name
class Person:
name = Name()
İlk bakışta biraz karmaşık gözüküyor olabilir, ama basit bir mantıkla
kurulduğunu __set__
ve __get__
metotlarını sindirdikten sonra
anlayabiliriz. Şimdi birkaç çıkarım/tanımlama yapalım:
- Descriptor’lar class gövdesinde initialize edilir. Her descriptor kendi başına bir class’dır.
__set__
metodu attribute değişiminde çağrılır, yani özetle erişimi bir metot ile sarmış oluyoruz. Benzer bir şekilde bu durum__get__
için de geçerli, bu metot da erişim zamanında çağrılır.- Eğer bir class
__get__
,__set__
ya da__delete__
tanımı yapıyorsa, bir descriptor olur.
Sonuç olarak descriptor’lar “nokta” ile yapılan işlemleri bir metoda sararak dinamik erişim ve değişim özellikleri sunuyor. Bu metotların kullanımını değerlendirerek çeşitli işlevselliğe ulaşabiliriz. Bu bağlamda validation işlemleri için kullandık mesela.
__delete__
metodu del person.name
için bir özelleştirme sunuyor, fakat
bunun üzerinde fazla durmayacağım.
Eğer önceden bir framework’ta buna benzer bir yapı kullandıysanız, syntax’i size tanıdık gelmiş olabilir. Örneğin Django’da ve pek çok ORM’da şu yapı vardır:
class Person(models.Model):
name = models.CharField()
Peki Django bu yapıyla ne yapıyor? __set__
ve __get__
metotlarını
kullanarak ilgili bilgiyi arkada sırasıyla database’e yazıyor veya çekiyor.
Şimdi aklımıza gelmesi gereken bir soru, Django database’da tabloya yazarken veya bilgiyi çekerken kolon ismini nasıl biliyor? Yani şu query’i yapabilmek için “name”nin bir yerden gelmesi lazım:
SELECT name
FROM users
WHERE user.id = 1
Burada da imdadımıza __set_name__
yetişiyor. Bu metot bize descriptor’un
nasıl isimlendirildiği konusunda bilgi veriyor. Örnek verelim:
class Name:
names = []
current_name = None
def __set_name__(self, owner, name):
self.field_name = name
...
Eğer bundan sonra, descriptor ne diye isimlendirilmiş merak edersek
self.field_name
kullanabiliriz. Bizim bağlamımızda bunun değeri name
olacaktır.
Önceden bahsettiğim üzere __set_name__
de metaclass üzerine bir
soyutlaştırma, ve metaclass’a nispeten yeni bir özellik.
Descriptor konusunda ufkunuzun açılması için Python dokümantasyonundan bir örnek vereceğim. Buradaki descriptor verilen dizindeki dosya sayısını dinamik olarak almaya yarıyor:
class DirectorySize:
def __get__(self, instance, owner):
return len(os.listdir(instance.dirname))
class Directory:
size = DirectorySize()
def __init__(self, dirname):
self.dirname = dirname
Tabii bu yapıyı daha basit bir şekilde class içinde de halledebilirdik, ama fikrimce bu örnek descriptor’ların dinamik yapısını iyi bir şekilde gösteriyor.
Descriptor’ların da __init__
metotları olduğundan, tanımlarken çeşitli
argümanlar da verebiliyoruz. Mesela Django CharField
için max_length
gibi
bir argüman alabiliyor bu da database bağlamında VARCHAR
boyutunu belirliyor.
Sonuç olarak descriptor’lardan anlamamız gereken şunlar:
- Bir class’ın attribute erişimini ve değişimini kontrol eden mekanizmalara
descriptor diyoruz ve bu işlemler sırayla
__get__
ve__set__
metotlarına denk geliyor. - Descriptor’lar özellikle data validation veya dinamik data erişimi için işimize yarıyor.
- Descriptor’lar argüman alabiliyorlar ve
__set_name__
metoduyla bir descriptor’un nasıl isimlendirildiğini öğrenebiliyoruz. - Descriptor’lar sadece class variable olarak tanımlandıkları zaman çalışırlar.
Son olarak Python’daki classmethod, staticmethod ve property gibi decorator’lar de descriptor yöntemiyle çalışıyorlar. Örneğin class’daki bir metodu property decorator’u ile decorate ettiğimiz zaman bir descriptor tanımlanmış oluyor ve bu descriptor arkada decorate edilmiş metodu çağırıyor.
Dikkat ettiyseniz, metaprogramlama yapıları birbiriyle iç içe geçmiş durumda, “aslında descriptor olan bir decorator” veya “metaclass yoluyla kendini manifesto eden decorator’lar” da bunlara örnek. Demek ki metaprogramlama yapıları birbiriyle çok ilişkili ve bir harmoni içinde kullanıldıkları zaman oldukça işlevsel özellikler sunuyorlar. Bu bağlamda yine metaprogramlama yapabilmek için dilin kendisini ve veri yapılarını çok iyi şekilde anlamamız gerektiği ortaya çıkıyor.