Hakkında Künye

Haskell ile Fonksiyonel Programlama

Merhaba sevgili e-bergi okuyucuları! Bu ay sizlere saf fonksiyonel bir dil olan Haskell'i kullanarak, fonksiyonel programlamanın alışılagelmiş dillerden farklı olan yaklaşımını aktarmaya çalışacağım. Genelde nesne tabanlı veya zorunlu programlama paradigmalarıyla programlamayı öğrenmiş insanlar için fonksiyonel programlama dillerini kullanmak çok zor gelir. Halbuki, çoğu zaman zorunlu veya nesne tabanlı yazılmış kodların uzunluğu fonksiyonel programlamaya kıyasla oldukça fazladır.

Fonksiyonel dillerin en önemli özelliği, yazılan programın yaptığı hesaplamalara matematiksel birer fonksiyon olarak bakmaları. Bu bakış açısı bir çok hesaplamayı kısa matematiksel tanımlamalarla yapmaya olanak kılıyor. Bunun yanı sıra saf fonksiyonlar programlamaya bir çok katkıda bulunmaktadır. Peki saf fonksiyonlar ne demek? Saf fonksiyonlar, hiçbir şekilde yan etkilerden etkilenmeyen ve yan etkileri olmayan fonksiyonlardır. Yani programın veya bir “değişkenin” hangi durumda (değerde) olduğundan bağımsız olarak değeri aynı kalan fonksiyondur. Saf fonksiyonlar aynı parametrelerle her zaman aynı sonucu dönerler. Şimdi saf fonksiyonların bazı yararlarından bahsedelim. Saf fonksiyonların yan etkileri bulunmadığı için değeri önemli değilse fonksiyon hesaplanmak zorunda değildir. Saf fonksiyonun değeri sadece parametrelerine bağlı olduğu için eğer fonksiyon aynı parametrelerle birden çok kez çağırılacaksa, bu değerler bir yerde saklanılabilir. Bu sayede fonksiyon bir dahaki çağrılışında baştan hesaplanmak zorunda değildir. Eğer iki tane saf fonksiyonun arasında verisel bir bağlılık yoksa yani birinin değerini diğeri kullanmıyorsa hangi sırada hesapladığınızın da bir önemi yoktur. Dolayısıyla, bunları farklı işlemcilerde paralel hesaplamanın da bir sakıncası yoktur. Son olarak da eğer dil yan etkilere kesinlikle izin vermeyen bir yapıdaysa derleyici hesaplamaları istediği sırada yapabilir ve istediği sırada birleştirebilir. Bu da derleyicinin optimizasyonunu kolaylaştırır.

Haskell'in saf fonksiyonel yapıda olması, ona yukarıda anlattığım özellikleri sağlıyor. Yalnız Haskell'in diğer bir önemli özelliği de tembel bir hesaplama mekanizması olması. Saf fonksiyonel olduğu için değer gerekmedikçe, Haskell hesaplama yapmıyor. Yalnız burada önemli nokta şu; sizin bir fonksiyonu belirli parametrelerle çağırıyor olmanız, o parametrelerin fonksiyona verilmeden önce hesaplanmasını gerektirmez. Mesela bir faktöriyel fonksiyonu ve bir de aldığı parametrelerden bağımsız olarak 5 dönen bir fonksiyon tanımladığınızı düşünün. Bu tanımladığınız ikinci fonksiyona parametre olarak faktöriyel fonksiyonu, faktöriyele de parametre olarak 1.000.000.000 verdiğinizi düşünün. O zaman Haskell faktöriyeli hiç hesaplamadan size direk 5 dönecektir. Bu tembel hesaplama mekanizması yan etkilerin olduğu bir dilde tabiki özel uğraşlarla yapılmadığı sürece mümkün değil. Tembel hesaplamanın programın çalışma hızını artırmak dışında kattığı bir yetenek de sonsuz veri yapılarına imkan tanımasıdır. Eğer siz bu yapının tamamına direk erişmek istemiyorsanız, sonsuz veriler tanımlamanızda çok fazla bir sakıncası yoktur.

Haskell ile birkaç örnek yapmadan önce nesne tabanlı veya zorunlu programlama dilleriyle arasında olan önemli birkaç farkı daha açıklamam gerekiyor. Haskell girdiler üzerine sıralı olarak yapılan işlemlerin ardından sonuç veren bir yapıya sahip olmaktan ziyade girdilerin üzerinde “dönüşümler” uygulayarak sonucu bulmaya çalışır. Bence fonksiyonel programlama da anlaşılması en önemli olan nokta budur. Mesela elinizde sayılardan oluşan bir liste var ve bu listedeki tüm elemanların toplamını hesaplamak istiyorsunuz. Bunu hesaplayacak fonksiyonun adına “listeTopla” diyelim. Haskell'de for-döngüsü yoktur. Bir döngüyle tüm elemanların üzerinden geçemezsiniz. Onun yerine tüm elemanların toplamının ne demek olduğunu düşünmeniz gerekir. Tüm elemanların toplamı, aslında listenin ilk elemanın değerine diğer tüm elemanların değerlerinin eklenmiş halidir. Listenin ilk elemanına ulaştıktan sonra listeTopla fonksiyonunu listenin kalan elemanlarıyla çağırırsanız, listenin ilk elemanın değerini ve kalan elemanlarının toplam değerini biliyorsunuz demektir ve bunları toplamanız aradığınız cevabı size verecektir.

Bunu Haskell'in sözdizimine uygun bir şekilde koda dökelim.

--Eğer liste tek elemanlıysa;
listeTopla [tekEleman] = tekEleman
--Birden fazla eleman varsa;
listeTopla (ilkEleman:digerElemanlar) = ilkEleman + (listeTopla digerElemanlar)
--Tabi fonksiyon boş listeyle çağrılırsa program hata verecektir ama istersek onu da 0'a eşit olarak tanımlayabiliriz.
listeTopla [] = 0

Şimdi tembel hesaplamayla ilgili yukarıda anlattığım örneği koda dökelim.

faktoriyel n = product [1..n]
-- [1..n] 1'den n'e kadar olan tam sayıların listesini oluşturur.
-- product fonksiyonu da Haskell'in kendisinde tanımlı olan bir fonksiyondur ve listenin tüm elemanlarını çarpar.

sabitFonksiyon herhangiBirSey = 5

Bunları satırları .hs uzantılı bir dosyaya yazıp bir Haskell yorumlayıcısından bu dosyayı yüklerseniz ve daha sonra "faktoriyel 10000" gibi bir komutla faktoriyel fonksiyonunu çağırırsanız muhtemelen biraz zaman aldığını farkedersiniz. Daha belirgin bir şekilde görmek için daha yüksek parametrelerle de deneyebilirsiniz. Ancak eğer "sabitFonksiyon (faktoriyel 10000)" komutunu kullanırsanız cevabın hemen döndüğünü göreceksiniz.

Bu ayki son örneğimde yine kısa olacak. Yukarıda [1..n]'in ne anlama geldiğini yazmıştım. Yalnız Haskell'in sonsuz verilere izin veren yapısı, sizi bir üst sınır belirlemek zorunda bırakmıyor. Yani [1..] yazdığınızda 1'den başlayarak, birer birer artan sonsuz bir listeyi ifade etmiş oluyorsunuz. Birer birer artmasını istemiyorsanız. Şu şekilde bir ifade kullanabilirsiniz. [1, 3.5 ..] bu ifade 1'den başlayıp 2.5 eklenerek artan bir listeyi ifade etmektedir.

--length fonksiyonu da Haskell'in listeler için kendinden tanımlı bir fonksiyonudur ve listenin uzunluğunu verir. mesela (length [523.32, -3.4]) ifadesi hesaplandığında 2 değerini dönecektir. length [[1..]] Yukarıdaki komutta length fonksiyonuna içinde liste olan bir liste verilmiştir ve hatta içerideki liste sonsuz elemanlıdır. Ancak dışarıdaki listenin tek bir elemanı vardır. Tembel hesaplama sayesinde Haskell bu sorgunun da cevabını verebilecektir. Önümüzdeki ay Haskell'in söz diziminden ve Haskell'deki veri yapılarından bahsedeceğim yazımda kaldığımız yerden devam etmek üzere, görüşmek dileğiyle kendinize iyi bakın.

Üstün Yıldırım
- 2 -