Selamlar herkese bugün “Zayıf tipleme ve Güçlü tipleme nedir? Dinamik tipleme ve Statik Tipleme Neyin Nesi? Ördek!? " başlıklı yazımıza göz atalım. Tip, şu programlarınızı yazarken ortaya çıkan değerin ne olduğunu ifade eder. Yani mesela diyelim Python kodluyorsunuz, meşhur input fonksiyonu ile gittiğiniz kullanıcıdan bir ifade aldınız. Hop, o değer artık Python diliyle “str”, yazılım jangonunda “String”tir. Bu stringi siz bir sayı ile toplayabilir misiniz? Python, bu mantıksız işleme karşı bir durup yüzünüze bakar; olması gereken de budur.

1
2
3
4
5
python3
>>> "helva" + 5
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

Üstte gördüğünüz gibi, Python adeta “Napıyorsun sen ya? İnt ile Str birbirine karışır mı Allah’ın cezası?” diye çıktı verdi. Tamam, peki ya aynı işlemi JavaScript ile yapsaydık? Şimdi şamatayı görün.

1
2
3
4
5
6
7
node
> "helva" + 5
'helva5'
> "5" + 5
'55'
> 5 + 5
10

Öyle bir bıkmışlık, bezmişlik var ki JavaScript’in üzerinde… Gelen intmiş metinmiş falan hiç kafaya takmıyor, yapıyor geçiyor.

Python ve JavaScript arasındaki fark, güçlü tipleme ve zayıf tipleme farkıdır. Güçlü tiplemede, bir tipin ne olduğu bellidir ve bir tip başka bir tipin yerine geçmez, programcı tarafından belirtilmiş bir ifade olmaksızın dönüşemez. Zayıf tiplemede ise bir tip başka bir tipmiş gibi davranabilir, birbiri arasında geçişlilik sağlayabilir ve programcının komutu olmadan da bir int günün sonunda string olabilir.

Dilleri güçlü tipleme ve zayıf tipleme arasında değerlendirirken kesinlikle bu zayıf tipleme kullanan bir dildir, bu güçlü tipleme kullanan bir dildir diye bahsedemiyoruz. Çünkü güçlü tiplemeyi en sert uygulayan diller bile bazı zayıf tipleme sistemlerini kullanabilir. Mesela ekrana “5” sayısını yazdırmak için, print fonksiyonuna 5 integerını/sayısını yollayabilirsiniz. Eğer 5 sayısını yazdırmak için illa ki onu bir stringe dönüştürmek zorunda kalsaydık, Python daha da güçlü tipleme kullanan bir dil olurdu. Tabii o da kendince eziyet oluyor.

Peki ya bir kıyas yaparsak, hangisi daha iyidir? Elbette ki güçlü tiplemeyi zayıf tiplemeye tercih ederim. Çünkü konu ortaya çıkan koddan ne kadar emin olabileceğinizle alakalı. Sürekli arkanızdan iş çeviren ve bir şeyin yerine başka bir şey koyan bir dille çalışmak sizi yormaz mıydı?

Güçlü tipleme beklenmeyen hatalardan ve pek çok yanılgılardan korur, doğrudur. Ancak her zaman sert bir kural mekanizmasıyla çalışmak yerine biraz daha tolerans isteyebiliriz. Mesela ki, “ ‘helva’ + 5 “ durumunda ne anlatmak istediğimiz gayet belli. Helva’nın yanına bir beş ekleyiver işte! Python aynen şu şekilde düşünür: “Ona izin ver buna izin ver, yarın ciddi bir durumda ne olur hâlimiz?” JavaScript ise şu şekilde: “Amaaan… Altı üstü bir web sayfası, çökse ne olur çökmese ne olur”

Elbette bir web sayfasındaki bozukluk fark edilmeyebilir, fark edilse de aman aman bir soruna gebe olmayabilir. …mi acaba? JavaScript’in ilgili tip sisteminin yarattığı bozuklukları gidermek adına epey bir bütçe ile TypeScript diye bir dil tasarlanıyorsa, işin ne kadar önemli olduğunu anlayabiliriz.

Ancak yine de, zayıf tipleme kullanan dillerin illa ki önemsiz projelerde kullanılan diller olduğunu düşünmeyin. Mesela ki, C’yi inceleyelim.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
touch çok_zayıfım_ben.c
echo '#include <stdio.h>  

      int main() {
         char yazi[] = "Ben aslında bir stringim";
         printf("%d", yazi);
         return 0;
      }' > çok_zayıfım_ben.c  
gcc çok_zayıfım_ben.c -o ./çok_zayıfım_ben && ./çok_zayıfım_ben
471692864                                                                                                                                                                               

Metin editörü tercihimin tuhaf olduğunu düşünebilirsiniz. Adım adım ne yaptığımı şöyle izah edeyim:

1- çok_zayıfım_ben.c diye bir dosya oluşturduk

2- echo komutuyla beraber bu dosyanın içeriğini doldurduk.

3- Bir c derleyicisiyle beraber de bu dosyayı derleyip çalıştırdık.

Gördüğünüz üzere, bir string (ya da karakter/char dizisi) olduğunu ayan beyan ilan ettiğimiz “yazi” değişkeni kelalaka bir sayıya dönüşebiliyor. Hatta işi ilerletip şöyle bir şey yapalım:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
echo '#include <stdio.h>  

       int main() {
         int yazi = "Ben aslında bir stringim";
         printf("%s", yazi);
         return 0;
      }' > çok_zayıfım_ben.c
gcc çok_zayıfım_ben.c -o ./çok_zayıfım_ben && ./çok_zayıfım_ben
çok_zayıfım_ben.c: 'main' işlevinde:
çok_zayıfım_ben.c:4:15: UYARI: initialization of 'int' from 'char *' makes integer from pointer without
a cast [-Wint-conversion]
   4 |    int yazi = "Ben aslında bir stringim";
     |               ^~~~~~~~~~~~~~~~~~~~~~~~~~
Ben aslında bir stringim

Hayda, C sorgusuz sualsiz bir metni Int olarak kabul edip derledi. C derleyicisi “Abi bak emin misin?” diye bir feryat okusa da, görünüşe göre programın çalışmasında hiçbir sorun gerçekleşmedi.

C, tıpkı JavaScript gibi zayıf tipleme kullanılan bir dildir. Ancak C’nin zayıf tipleme anlayışı ile JavaScript’in zayıf tipleme anlayışında bir fark vardır. C için her şey sayıdan ibarettir, bu yüzden hangi tip ilan edilirse edilsin özünde verinin sayısal değerine atıfta bulunulur. Ancak JavaScript’in yapısında sayısal değere yapılan atıftan ziyade, Ördek yazımı (Duck) denen bir sistem kullanılır.

Ördek yazımı şu şekilde tarif edilir; “Eğer bir şey paytak paytak yürüyorsa ve vaklıyorsa, o şey ördektir.”. Yani karşınızda paytak paytak yürüyen ve vaklayan bir goril olsa bile, bunu ördek olarak kabul edebiliriz. Tıpkı “helva + 5” örneğinde gösterdiğimiz gibi. Ancak, dikkat edilmelidir ki JavaScript’i kıyasladığımız Python da ördek tiplemesi kullanılan bir dildir. Mesela ki, tiplemenin çok çok katı olduğu Rust ile Python’u kıyaslayalım.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
python3
>>> def vakayı_vakvakiye(sayı):
...     print(sayı + 0.10)
...  
>>> vakayı_vakvakiye(10)
10.1
>>> vakayı_vakvakiye(0.4)
0.5
>>> vakayı_vakvakiye("helva")
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<stdin>", line 2, in vakay_ı_vakvakiye
TypeError: can only concatenate str (not "float") to str

Bir fonksiyona 0.1 değeri ekleyip ekrana yazdıran bir fonksiyon tanımladık. Noktasız, int olarak tiplenen “10” değerini fonksiyona yolladık, hiçbir şey olmadı. Float olarak tanımlanan “0.4”ü yolladık, ona da bir şey demedi. Ancak “helva” değerini yollayınca Python, “Kardeşim deli misin, Helva ile 0.1’i hangi mantığa göre toplayayım?” diyerek adeta “Bunun ördeğe benzer bir tarafı var mı?” demiş oldu.

Rust ise birazcık farklıdır.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
echo 'fn vakayi_vakvakiye(sayi: f64) {
          println!("{}",sayi + 0.1);
      }
      fn main() {
          vakayi_vakvakiye(0.1);
          vakayi_vakvakiye(10);
      }' > derlenmeyecek_ki.rs  
rustc derlenmeyecek_ki.rs && ./derlenmeyecek_ki
error[E0308]: mismatched types
--> derlenmeyecek_ki.rs:6:22
 |
6 |     vakayi_vakvakiye(10);
 |                      ^^
 |                      |
 |                      expected `f64`, found integer
 |                      help: use a float literal: `10.0`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

Rust, her namuslu dilin yapması gerektiği gibi “Sen float demişsin de bana integer geldi? Ne iş?” diyerek derlemeyi kesti ve greve çıktı. Çünkü biz f64 göndereceğimizi vaat ettik, Python’a böyle vaatte bile bulunmadık!

Rust ile Python güçlü tipleme kullanan iki ayrı dildir ancak Rust aynı zamanda “Statik tipleme” de kullanır. Statik tiplemede, her değerin tipinin çalışma anından önce kesinleştiği varsayılır.

Statik tipleme kullanılan dillerin çoğunluğunda tip bildirimi bulunur ve C de zayıf tiplemeli olmasına karşın aynı zamanda statik tiplemeli bir dildir. Ancak statik tipleme kullanılmasına karşın tip bildirilmeyen bazı diller de vardır. Hatta, ördek yazımına çok benzer yaklaşımlar bile görebilirsiniz. Mesela Crystal’e bakalım:

1
2
3
4
5
6
7
8
9
touch bu_da_böyle_bir_dil.cr
echo 'def vakayı_vakvakiye(sayı)
          puts(sayı + 0.1)
      end
      vakayı_vakvakiye(10)
      vakayı_vakvakiye(0.1)' > bu_da_böyle_bir_dil.cr
crystal build bu_da_böyle_bir_dil.cr && ./bu_da_böyle_bir_dil  
10.1
0.2

Python’u çok andırıyor, değil mi? Ancak Crystal, Rust kadar olmasa da sıkı bir tipleme mekanizmasına sahiptir. Şu örneği inceleyelim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
touch acep_nedir.cr
echo "def ne_donecek_ki_acaba(sayı)
          if sayı > 10
              return 20
          end
      end
      bakalım_ne = ne_donecek_ki_acaba(20)
      puts typeof(bakalım_ne)" > acep_nedir.cr
crystal build acep_nedir.cr && ./acep_nedir
(Int32 | Nil)

Eğer Python’da bu fonksiyonu yazmış olsaydık, bakalım_ne isimli değişkenin tipi muhakkak Int olurdu. Ancak Crystal, hem Nil (yani yokluk) tipini hem de Int32 tipini kabul ediyor. Çünkü fonksiyona eğer 5 vermiş olsaydık, hiçbir şey geri dönmeyecekti. 20 vermiş olmamız bir şeyi fark etmiyor çünkü fonksiyonu yazmaya bitirdiğimiz zaman tipi belirlenmişti.

Python gibi dinamik tiplemeli dillerde ise tip, fonksiyon çalıştıktan sonra belirlenir. Tip, değerin geldiği yere bakılmaksızın o anda elde tutulan değere göre belirlenir. Bu yüzden olası hatalar derleme anında, çalışmadan önce değil o anda fark edilir. Mesela verdiğimiz Crystal örneğinde Nil dönmesi, değerin Nil olup olmadığı sorgulanmadan dalgınlıkla yapılacak bir hesaplamanın da önüne geçilmiş olur.

Ancak, Crystal’in getirdiği bu birleşke tip yapısına yine statik tipleme kullanılan Rust’ta rastlanılmaz. Rust, kendisine özgü enum tipleriyle bir tip altında birden çok seçenek sunmayı tercih eder.

Statik tip yapısının dinamik tipleme üzerine daha çok tercih edildiğini söylemek gerekir. Öyle ki dinamik tipleme denince akla gelen iki dil olan Ruby ve Python’da bile tip bildirimlerine ve statik tiplemeye destek geliyor. Bugünün trendi, güçlü ve statik tiplemeli diller. Zira bu tipleme sistemi pekâlâ yazılımcıya ekstra külfet yüklüyormuş gibi görünse de, yazılımın hata düzeltmelerine büyük ölçüde yardımcı oluyor ve bakım zahmetini azaltıyor. Yine de, dinamik tiplemenin de hiç de küçümsenmeyecek taraftarlarının bulunduğunu da söylemek gerekir.

Yazıyı özetleyelim:

-Güçlü tipleme, yazılımcının kesin bir tip dönüşümü bildirmeden dönüşüm yapılmayan tipleme türüdür. Örnek: Python, Ruby, Rust, Go, Java, C#

-Zayıf tipleme, bir verinin tipinin kendiliğinden dönüşebileceği ve tipin ne olduğunun dikkatlice takip edilmesi gereken tipleme türüdür. Örnek: PHP, JavaScript, C, C++

-Statik tipleme, verinin tipinin en başından belirlenmesi ve bütün programın buna göre şekillenmesidir. Örnek: Rust, C, C++, Go, C#, Java

-Dinamik tipleme, verinin son anda aldığı değere göre tipinin belirlenmesidir. Ördek: Python, Ruby, JavaScript, PHP.

Yazıyı bitirirken daha fazla okuma için şu kaynakları bırakmayı uygun görüyorum, bol okumalar!

https://stackoverflow.com/questions/46207205/what-is-typing-discipline/46207303

https://en.wikipedia.org/wiki/Type_system