Programlama Dillerinde Statik ve Dinamik Tür Kavramı

C ve Python örnekleri ile statik ve dinamik tür kavramına bakıyoruz

Programlama Dillerinde Statik ve Dinamik Tür Kavramı

Programlama dillerinin (neredeyse ?) tamamında bir tür kavramından bahsedebiliriz. Tür, İngilizce type kelimesinin karşılığıdır. Genelde tüm programlama dillerinde, yazılan kodda tanımlanan değişkenlerin, sabitlerin, oluşturulan ifadelerin birer türleri vardır. Dillerdeki tür ile ilgili kavramları kapsayan çatı terim ise Tür Sistemi yani Type System'dir.

Dilde türe sahip olan değişken (nesne), ifade gibi kavramların türleri, eğer derleme sırasında belirleniyorsa o diller statically typed yani statik türlü ya da statik türe sahip olan dil, eğer çalışma sırasında (run time) belirleniyorsa da dil dynamically typed yani dinamik türlü ya da dinamik türe sahip olan dil olarak adlandırılır. İlk paragrafta da belirttiğim gibi type kelimesinin buradaki Türkçe karşılığı yazmak değil tür dür. Statik ve dinamik kelimeleri de değişmeyen/sabit ve değişen/sabit olmayan anlamlarından ziyade (yazının ilerleyen kısımlarında aslında bunu da ima ettiklerini göreceğiz) derleme zamanında ve çalışma zamanında anlamındadır. Nasıl derleme zamanında bağlanan kütüphanelere statically linked library, çalışma zamanında bağlanan kütüphanelere de dynamically linked library diyorsak buradaki kelimeler de bu anlamdadır.

Biraz daha genelleştirelim...

ÖzellikStatikDinamik
Türlerin belirlenme zamanıderleme zamanı (compilation time)çalışma zamanı (run time)
Tür ile ilgili kontrollerderleme zamanı (compilation time)çalışma zamanı (run time)
Tür ihlali bildirimleriderleme hataları ve uyarılarıistisna üretilmesi (exception throwing)
Derlenen / Yorumlananderlenen (compiled) dilleryorumlanan (interpreted) diller
Değişkenlerin tür bildirimleriaçıkça (explicit)örtülü (implicit), çıkarımlı (inferred)
Değişken türlerisabittir, değişmezsabit değildir, değişebilir

Bu konudaki temel tanım, türlerin ne zaman belirlendiğidir, yani tablodaki ilk satır. Diğer satırlar ise ilgili dillerin sahip oldukları genel geçer, tipik özellikleridir. Yüzlerce programlama dili olduğunu düşünürsek tüm satırların tüm diller için geçerli olduğunu söylemem fazla iddialı olabilir ama pratikte bu şekilde olduğunu düşünmek de sizi fazla yanıltmayacaktır.

C, C++, Java gibi diller statik tür kavramına sahip iken, JavaScript, Python, PHP gibi diller ise dinamik tür kavramına sahiptir [1, 2]. Şimdi gelin, C ve Python dillerinden örneklerle konuyu biraz daha anlayalım.

Python, Dinamik Tür Kavramına Örnek

Python dilinde type() isimli fonksiyon ile bir nesnenin türünü öğrenebilmek mümkündür. Aşağıdaki koda bir bakalım:

x = 3
print (type(x))
x = "alper"
y = x + " yazar"
print (type(x))
x = 3.5
print (type(x))
y = x + " yazar"

Çıktı da şu şekilde olmaktadır:

<class 'int'>
<class 'str'>
<class 'float'>
Traceback (most recent call last):
  File "<string>", line 8, in <module>
TypeError: unsupported operand type(s) for +: 'float' and 'str'

Durumu bir değerlendirelim:

  • x değişkenin türü farklı türden değerler atayarak örtülü olarak belirlenmiş ve değişmiştir. x değişkenini tanımlarken açıkça türünü belirtmedik, ona atanan değere göre türü otomatik olarak belirlendi.

  • y = x + " yazar" ifadesi hem satır 4 hem de satır 8 de vardır. İlk ifadede bir problem yoktur çünkü ilgili satırda x in türü str dir ve dilin kuralları gereğince iki string toplanabilir. Fakat, ikinci ifadede hata vardır çünkü o noktada x in türü float tır ve yine dil kuralları gereği str ile float toplanamamaktadır. Çalışma sırasında bir değişkenin türü değiştirilebilmiştir.

  • Son satırdaki hata ise ancak çalışma sırasında TypeError olarak yakalanabilmiştir. Peki niye programın çalışmasının başında yakalanamamıştır? Çünkü Python dilinde kodlar tipik olarak satır satır çalıştırılır ve hata ilgili satır çalıştırıldığı zaman ortaya çıkmıştır.

C, Statik Tür Kavramına Örnek

Benzer bir örneği C ile yapalım. C dilinde, Python'daki gibi dahili (built-in) bir type() fonksiyonu yoktur. Fakat dile C11 ile gelen generic selection expression [5] desteğini bir makro ile kullanarak benzer bir kod oluşturabiliriz [3, 4].

// See: https://stackoverflow.com/a/17290414/1766391
// Tested with x86-64 gcc 12.2 with default flags on https://godbolt.org/
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

#define typename(x) _Generic((x),        /* Get the name of a type */             \
                                                                                  \
        _Bool: "_Bool",                  unsigned char: "unsigned char",          \
         char: "char",                     signed char: "signed char",            \
    short int: "short int",         unsigned short int: "unsigned short int",     \
          int: "int",                     unsigned int: "unsigned int",           \
     long int: "long int",           unsigned long int: "unsigned long int",      \
long long int: "long long int", unsigned long long int: "unsigned long long int", \
        float: "float",                         double: "double",                 \
  long double: "long double",                   char *: "pointer to char",        \
       void *: "pointer to void",                int *: "pointer to int",         \
      default: "other")

int main(void){
    int x;
    double y = 3.4;
    printf("x bir %s\n",typename(x));
    printf("y bir %s\n",typename(y));
    x = y; //Neden hata değil?
    printf("x hala %s\n",typename(x));
    printf("x = %d\n",x);
}

Çıktı da şu şekilde olmaktadır:

x bir int
y bir double
x hala int
x = 3

Duruma bir bakalım:

  • Yukarıda iki farkı değişken tanımladık: x ve y. Tanımlama sırasında ise türleri açıkça belirttik, x bir int türünden nesne, y ise float türünden bir nesne oldu. Artık bu değişkenlerin türleri programın ömrü boyunca sabit kalacaktır.

  • Akışın ortalarında bir yerde 3.4 değerini tutan ve float türünden olan y nin değerini bir int türden değişken olan x e atadık. Peki bu niye problem olmadı? Hani türler değişimiyordu? Aslında yine değişmedi, x hep int. C dilinin kuralları gereğince aslında 3.4 bir implicit type conversion a yani örtülü tür dönüşümüne maruz kaldı. Bu atamada sanki değeri 3 olan gizli ve geçici bir int nesne yaratıldı ve bu nesnenin değeri x e atandı. Bu otomatik çevrim, dilin statik/dinamik türe sahip olması ile ilgili değildir. Otomatik çevrim konusu dilin weakly/strongly typed bir dil olması ile ilgilidir.

  • x e ne yaparsak yapalım türünü değiştiremedik.

Hadi bir örneğe daha bakalım:

// Tested with x86-64 gcc 12.2 with default flags on https://godbolt.org/
int main(void){
    int *p1, *p2, *p3;
    p3 = p1 + p2;
}

Derleyici çıktısı

<source>: In function 'main':
<source>:3:13: error: invalid operands to binary + (have 'int *' and 'int *')
    3 |     p3 = p1 + p2;
      |             ^
ASM generation compiler returned: 1

Peki buradaki sorun nedir? C kuralları gereği pointer türden iki değişken toplanamaz. Bu davranış konumuz ile ilgili değil. Burada vurgulamak istedğim, Python'dakinin aksine bir kural ihlalinin derleme sırasında yakalanması. Çünkü türler derleme sırasında belirlenir ve kurallar hemen kontrol edilebilir. Dikkat ederseniz derleyici hatasından dolayı daha çalıştırılabilir dosya bile oluşturamadık. Yani çalışma zamanına dahi gelemedik.

İlgili

🧐
Bu yazının konusu değil ama yukarıdaki kaynakların ikisinde, C++ dili C'den daha weakly typed gösterilmiş. C++ bilgim sınırlı ama durumun hiç de öyle olmadığını düşünüyorum. Örneğin C++ dili, örtülü tür dönüşümü konusunda C'ye kıyasla çok daha katı kurallara sahip.

Aşağıdaki video JavaScript ile ilgili ama konsepti güzel açıklıyor.

Kaynaklar

Referanslar