Herkese merhaba sevgili e-bergi okurları. Adını ilk defa duyduğunuz bir programlama dilini merak edip araştırdığınızda karşınıza kulağa tanıdık gelmeyen pek çok terim çıkabilir. Bu terimler programlama dillerini sınıflandırmak içindir ve dilin iç işleyişi hakkında bilgi verir. Bu sınıflandırmalardan birisi de veri tiplerinin nasıl işlendiğini belirleyen tip sistemi dediğimiz yapılar üzerinden yapılır. Bu yazımızda detaylara girmeden veri tiplerinden ve bazı tip sistemlerinden bahsedeceğiz ve yazımızın sonuna geldiğimizde statically typed, duck typing gibi bazı kavramların ne ifade ettiğini açıklığa kavuşturmuş olacağız.

Type (Tip)

En kaba tabirle tip bir programda yer alan verilere atanan ve verinin hangi işlemlere tabi olabileceğini belirleyen bir özelliktir. Bir dilde fonksiyonlar ve operatörler sadece belirli tipler üzerinde işlem yapma yetkisine sahiptir. Örneğin C'de toplama operatörü iki int arasında veya bir int ve bir pointer arasında tanımlıyken iki pointer arasında tanımlı değildir. Bu yüzden aşağıdaki C kodunu compile etmeye çalıştığımızda aşağıdaki hata ile karşılaşırız.

#include <stdio.h>

int main(void)
{
    int *ap=NULL, *bp=NULL, *cp;
    cp = ap+bp;

    return 0;
}
$ gcc test.c 
test.c: In function ‘main’:
test.c:6:9: error: invalid operands to binary + (have ‘int *’ and ‘int *’)
    6 |  cp = ap+bp;
      |         ^

Tekrarlayacak olursak, bir programlama dilinde veriler çeşitli veri tipleriyle ilişkilendirilir ve bir verinin tipi bu verinin nasıl yorumlanacağını belirler ve hangi işlemlerden geçebileceğini sınırlar. Bu sınırlamalar sayesinde programın yazımı esnasında yapılan çeşitli hatalarda programın çalışması veya derlenmesi engellenir. Type error'ların (tip hatalarının) önüne geçerek type safety'e (tip güvenliğine) ulaşmaya çalışan bu mekanizmalara type system denir.

Type Systems (Tip Sistemleri)

Pensilvanya Üniversitesi'nde bilgisayar bilimleri profesörü Benjamin Pierce Types and Programming Languages adlı kitabında tip sistemlerini "yazılan programda üzerinde işlem yapılan verilerin tipini inceleyerek bazı davranışların yokluğunu garantilemeye çalışan gerçekleştirilmesi kolay yöntemler" olarak tanımlıyor. Yani tip sistemi sayesinde çalıştırılabilir bir C programında iki pointerı toplamaya çalışmadığımızdan emin olabiliriz.

Şimdiye kadar tip ve tip sistemleri hakkında verdiğimiz bilgiler bundan sonraki konuları anlamak için yeterli olduğu ve daha detaylı bilgiler iyi bir akademik temel gerektirdiği için bu konularda daha fazla detaya inmeyeceğiz. Bu noktada yazının başında bahsettiğimiz terimleri açıklamaya başlayabiliriz.

Weak ve Strong Typing

Aslında weak typing ve strong typing'in ne anlama geldiği hakkında bir fikir birliğine varılabilmiş değil. Konu hakkındaki Stack Overflow girdisindeki "Raúl" adlı kullanıcının cevabından yola çıkarak bu iki kavramı açıklamaya çalışalım. Az önce bahsettiğimiz gibi tip sistemleri yazılan programda bazı davranışların yokluğunun garantisini vermeye çalışır. Ancak bazı diller tip sistemlerinin bu çabasını tamamen etkisiz hale getirmemize sebep olan yapılar sunar. Örneğin C dilinde bir verinin tipi başka bir veri tipine açıkça belirtmeye gerek kalmadan dönüşebilmektedir. Bunun da ötesinde C dilinde herhangi bir pointer void pointerına, void pointerları da herhangi bir pointera dönüştürülebilir. Bu da aslında bir integerın adresini tutan pointerın gösterdiği verinin sanki başka bir tip veriymiş gibi işlememize izin verir.

#include <stdio.h>

int main(void)
{
    char *str="hello";
    int *ap;
    ap = str;
    printf("%d\n", *ap);

    return 0;
}

Yukarıdaki kodu derlemeye çalıştığımızda gcc uyarı verse de çalıştırılabilir program ortaya çıkarır.

$ gcc test2.c
test2.c: In function ‘main’:
test2.c:7:5: warning: assignment to ‘int *’ from incompatible pointer type ‘char *’ [-Wincomp
atible-pointer-types]
    7 |  ap = str;
      |     ^
$ ./a.out
1819043176

Mevcut koddaki char pointerını int pointerına dönüştürmeden önce void pointerına dönüştürdüğümüzde herhangi bir uyarı almayız.

#include <stdio.h>

int main(void)
{
    char *str="hello";
    int *ap;
    ap = (void*)str;
    printf("%d\n", *ap);

    return 0;
}
$ gcc test2.c 
$ ./a.out 
1819043176

Tip sisteminin kısıtlamalarında açıklar yaratmamıza izin verdiği için C diline weakly typed diyebiliriz. C'nin aksine strongly typed bir dilde bu tarz açıklara izin verilmez.

Static ve Dynamic Typing

Tip sistemlerinde belki de en çok öne çıkan özellik bir o sistemin static mi dynamic mi olduğudur. Basitçe ele alacak olursak statically typed bir dilde yazılan programda type error olup olmadığının kontrolü (yani type checking) derleme esnasında programın kaynak kodu incelenerek yapılır. Dynamically typed dillerde ise type checking işlemi programın çalışması esnasında yapılır. Washington Üniversitesi'nin bu kaynağından görüldüğü üzere dynamic typing ve static typing'i karşılaştırdığımızda ikisinin de avantajları ve dezavantajları var. Static typing'de yazılmakta olan programda bir type error olup olmadığını derleme esnasında fark etme fırsatı saatlerce hatta haftalarca çalışacak programları göz önünde bulundurursak type error'ların çalışma esnasında keşfedilmesinden çok daha avantajlı. Bununla birlikte static typing, type error'a sebep olmayacak bazı programları reddeder. Type error'a sebep olmayacak mümkün olan bütün programları onaylayacak bir static type checking metodu oluşturmak imkansızdır. Dynamic typing ise type error'a sebep olmayacak bütün programların çalışmasına izin verir.

Örneğin dynamically typed bir dil olan Python'da + operatörü string ve integer arasında tanımlı değildir ancak Python'da aşağıdaki kodda type error olmadığı için programımızı çalıştırabiliriz.

if True:
    a = "hello"
else:
    a = "hello" + 1

print(a)

Programımız çalıştığında çalışma esnasında hiçbir şekilde else kısmı çalışmadığı için type error'la karşılaşmayız.

$ python test3.py 
hello

Statically typed bir dil olan C'de ise kaynak kod incelenirken type error'a sebep olacak işlemin gerçekten çalıştırılıp çalıştırılmadığı sorgulanmaz. Bu yüzden gerçekte type error oluşturmayacak 'doğru' programlar sistem tarafından onaylanamaz.

#include <stdio.h>

int main(void)
{
    int *ap=NULL, *bp=NULL, *cp=NULL;

    if (0)
        cp = ap + bp;

    return 0;
}
$ gcc test3.c
test3.c: In function ‘main’:
test3.c:8:11: error: invalid operands to binary + (have ‘int *’ and ‘int *’)
    8 |   cp = ap + bp;

Yukarıda gördüğümüz üzere programımızın type error'a sebep olmayacağından emin olduğumuzhalde gcc programımızı derlemedi. Dynamic ve static typingin karşılaştırması hakkında daha fazla bilgiye yukarıdaki bağlantıdan ulaşabilirsiniz.

Nominal ve Structural Typing

Nominal ve structural tip sistemleri iki veri tipinin birbirine özdeşliğine karar verilirken izlenen yolları belirler. Nominal bir tip sisteminde iki veri tipi eğer ayrı ayrı tanımlandıysa pratikte özdeş olsalar bile özdeş kabul edilmezler. Structural tip sistemlerinde ise iki tipin özdeşliğine karar verilirken yapıları incelenir. Önceki konularda yaptığımız gibi C'den bir örnek verelim.

#include <stdio.h>

struct insan{
    int boy;
    int kilo;
};

struct kedi{
    int boy;
    int kilo;
};

void kedi_kilo(struct kedi ad)
{
    printf("%d\n", ad.kilo);
}

int main(void)
{
    struct insan bob={180,80};
    struct kedi frida={30,5};

    kedi_kilo(frida);
    kedi_kilo(bob);

    return 0;
}
$ gcc test4.c 
test4.c: In function ‘main’:
test4.c:24:12: error: incompatible type for argument 1 of ‘kedi_kilo’
   24 |  kedi_kilo(bob);
      |            ^~~
      |            |
      |            struct insan
test4.c:13:28: note: expected ‘struct kedi’ but argument is of type ‘struct insan’
   13 | void kedi_kilo(struct kedi ad)
      |                ~~~~~~~~~~~~^~

Gördüğümüz üzere nominally typed bir dil olan C'de kedi ve insan structları içinde aynı tip dataları barındırdığı halde argüman olarak kedi bekleyen bir fonksiyonda insanı kullanmamıza izin verilmiyor. Haskell gibi structurally typed bir tip sistemine sahip bir dilde ise tip sisteminin buna benzer işlemlere izin vereceğini ayrıca bazı dillerde bazı tipler için structural, diğer tipler için nominal typing kullanıldığını eklemek lazım.

Manifest ve Implicit typing

Manifest typing, static typing'in içinde yer alır. En basit haliyle bir programlama diline manifest typing kullanıyor diyebilmemiz için yeni oluşturulan verilerin tiplerinin programcı tarafından birtakım keywordler ile açıkça belirtilmesi gerekir ve örnek olarak C, C++ gibi dilleri verebiliriz. Bunun karşıtı olarak implicit (veya inferred) typing'in olduğunu söyleyebileceğimiz dillerde variable'ların veya fonksiyon argümanlarının tipleri nadiren belirtilir veya hiç belirtilmez. Implicit typing'den bahsettiğimizde aklımıza dynamic typing gelse de Haskell gibi tiplerin belirtilmesinin opsiyonel olduğu ve type error'ların derleme sırasında ortaya çıkarıldığı diller de vardır.

Duck typing

Duck typing, duck test'in (Eğer bir şey ördek gibi görünüyorsa, ördek gibi yüzüyorsa ve de ördek gibi ses çıkarıyorsa muhtemelen o şey bir ördektir.) programlama dillerine uygulanmış halidir. Duck typing'in olduğu dillerde bir objenin herhangi bir amaçla kullanılıp kullanılamayacağına çalışma esnasında o anda objenin sahip olduğu method ve diğer özelliklere bakılarak karar verilir. Structural typing'in aksine duck typing dinamiktir yani tip uyumluluğuna programın çalışması esnasında bakılır. Python duck typing'e örnek olarak verilebilir.