7 Haziran 2012 Perşembe

Haskell'de veri tipleri

        Daha öncede bahsettiğim gibi Haskell strongly typed bir dil olduğundan, veri tipleri hakkında bilgi sahibi olmak en önemli şeylerden biridir. Daha önceki Haskell kodlarında farkettiyseniz, değişken kullanırken veya fonksiyon yazarken tiplerini belirtmeden yazmıştık. Bunun nedeni Haskell'in tip çıkarsama(type inferencing) yapan bir dil olmasıdır. Tip çıkarsama yapan dillerde, dil sizin değişkeni kullanışınıza göre değişkene bir tip ataması yapar. Haskell statik tip bir dildir, yani tip ataması derleyici tarafından derleme zamanında gerçekleşir. Şimdi Haskell'deki yapısal(build-in) veri tiplerine şöyle bir bakalım:

İsimAçıklama
Int Tamsayı-232-1 ile 232-1 arasındaki değerleri tutabilir.
IntegerTamsayıInt kadar verimli değildir. Fakat sınırsız uzunlukta sayı tutabilir.
FloatOndalık sayıVirgülden sonraki kısımın kesinlik azdır
DoubleOndalık sayıVirgülgen sonrası için kesinlik değeri Float'ın 2 katıdır.
BoolMantıksalSadece doğru(True) veya yanlış(False) değerlerini tutabilir.
CharKarakterTek bir karakter tutabilir.

      Daha önce derleyici olarak ghc kullandığımı söylemiştim, ghc'nin bir güzel yanıda kendisine ait bir yorumlayıcısı olmasıdır. ghc'nin yorumlayıcısını kullanmak için linuxta komut satırına 'ghci' yazmanız yeterli olacaktır. Son olarak aşşağıdaki kodu anlamak için Haskell'de 'let' kelimesinin yorumlayıcı üzerinde geçici olarak değişken tanımlamanızı sağladığını bilmeniz şimdilik yeterlidir.  


Yukarıdaki kodda ghc'nin yorumlayıcısını kullanarak Haskell'deki yapısal tipleri göstermeye çalıştım. x değişkenine farklı değerler atadım daha sonrada ':t' komutuyla değişkene yorumlayıcının otomatik olarak atadığı tipi gösterdim. İlk satırda farkedeceğiniz üzere ::Int diye bir terim kullandım. Bu terim x değişkenine yorumlayıcının değilde benim istediğim tipin atanmasını sağlar, yani bir casting işlemi yapar. Peki ya casting yapmasaydım? Bu durumda 4. ve 6. satırlarda görebileceğiniz gibi yorumlayıcı otomatik olarak kendi istediği tipi yani Integer atayacaktı. Aynı şeyin Double ve Float tiplerindede olduğnu görebilirsiniz. Son iki 'let' komutuna bakarsak karakter ile string arasındaki farkıda görebiliriz. Son 'let' komutunun bize açıkca belirttiği şey; stringler aslında karakterlerden oluşan listelerden başka bişeyler değillerdir(Köşeli parantezler liste operatörleridir.). Listeleri daha ilerde inceleyeceğiz.

Aşşağıdaki Haskell kodunu ve çıktısını inceleyelim:

tip.hs
testFloat::Integer->Float
testFloat n = (fromInteger (toInteger n)) * 2.12345678911112

testDouble::Int->Double
testDouble n = (fromInteger (toInteger n)) * 2.12345678911112

main = do{    putStrLn "testFloat => ";
            print(testFloat 12);
            putStrLn "testDouble => ";
            print(testDouble 12);
        }


        Değişkenler üzerinde casting yaparak istediğimiz tipi atayabiliyoruz, peki ya fonksiyonlarda? Fonksiyonlardada bir fonksiyonun parametre olarak içine ne alacağını ve değerinin ne olacağını kendimiz belirtebiliriz. Yukarıdaki kodda  testFloat adlı fonksiyonun başına aşşagıdaki kodu koymasaydım fonksiyonun döndüğü değer derleyicinin otomatik atadığı değer yani Double olurdu.
testFloat::Integer->Float
Kod açıkça şunu ifade eder; testFloat parametre olarak Integer tip alır ve değeri Float bir tiptir. Bu ifadeyi daha iyi anlamak için hemen matematiksel fonksiyonları düşünelim. Biz matematiksel bir fonksiyon tanımlarken aslında yukarıdaki ifadeyi kullanırız:
f: \mathbb{Z} \!\, => \mathbb{R} \!\,
f(x) = x+π
Gördüğünüz gibi fonksiyonu tanımlarken tamsayılar kümesinden reel sayılar kümesine gittiğini gösterdik. Burda aslında x'in bir tamsayı, fonksiyonun değerininde bir reel sayı olduğunu söyledik. Aynı yukarıda kodumuzda, testFloat fonksiyonunun  Integer tipindeki sayılar kümesinden, Float tipinde sayılar kümesine gittiğini söylediğimiz gibi. Son olarak birden fazla parametre alan fonksiyonlarda yukarıdaki işin nasıl olduğuna bir bakalım:

us.hs
pow::Float->Float->Float
pow a b = a**b

main = print(pow 2 2)
Ilk satır biraz önceki gibi bir mantığa sahip değilmiş gibi gözükebilir ama aslında aynı mantık. pow fonksiyonunun söyle bir fonksiyon olduğunu düşünelim pow 2 x bu durumda ilk satırımızda şöyle olurdu pow::Float->Float. Yani Float tipinde bir parametre alıyor ve fonksiyonumuz Float tipinde. Peki fonksiyonumuz normalken: pow x y. Ozaman ilk satırımız pow::Float->(Float->Float) olur değilmi. Yani fonksiyonumuz Float tipinde bir değer alıp Float tipinden Float tipine giden bir fonksiyona dönüşüyor. Şöyle bir toparlamak gerekirse:
(pow 2 2) Float

(pow 2)(x) Float ->Float

(pow (y))(x) Float->(Float->Float)
Burada şöyle düşünülebilinir; birden fazla parametre alan bir fonksiyona parametrelerinden sadece birini verirsek aslında yeni bir fonksiyon elde ederiz. Bu kısım biraz karışık, ilerleyen zamanlarda yeniden inceleyebiliriz.

6 Haziran 2012 Çarşamba

Örneklerle Haskell'e küçük bir bakış

        İlk olarak buradan Haskell ile kod geliştirmek için bilgisayarınıza kurmanız gereken platform bilgilerini ve haskell hakkında değerli bilgileri bulabilirsiniz.


        Eğer Haskell'i bilgisayarınıza kurduysanız bir kaç küçük denemeyle başlayabiliriz. Ben linux kullandığım için linux'ta anlatacağım bu arada derleyici olarakta ghc kullanıyorum.
       Öncelikle adettendir diye bir "MerhabaDünya" programı yazalim. (Haskell kodunuzu yazdığınız dosyaların uzantısı .hs dir.)

merhaba.hs

main = putStrLn "Merhaba Dünya"

       Yukarıda kodu linuxta komut satırından ghc merhaba.hs -o merhaba komutuyla derledikten sonra ./merhaba komutu ile çalıştıra bilirsiniz.
      Çalıştırdığınızda görebileceğiniz gibi  kod ekrana "Merhaba Dünya" yazacaktır.
Şimdi kod üzerinde bir kaç şeye dikkat çekmek istiyorum. Fonksiyonel programlama dillerinde adındanda anlaşılacağı gibi herşey fonksiyondur. Kodda gördüğünüz gibi main fonksiyonunu tanımlıyoruz. Diğer programlama dilleri gibi Haskell'dede yürütme(execution) main'den başlar. Haskell'de fonksiyon tanımlamaları :

fonksiyonunAdı parametreleri = fonksiyonunVücudu

şeklindedir. Kodda diğer bir ilgi çekmek istediğim şey "putStrLn" fonksiyonu. "putStrLn" fonksiyonu kendisine parametre olarak verilen string'i ekranda yeni bir satıra basar.
      Şimdi verilen bir sayının asal olup olmadığnı söyleyen bir programın hem C'de hemde Haskell'de yazılmış örneklerini inceyelim. 

asal.hs
isPrime n q = if (n `mod` q) == 0 then 
                False
            else 
                if (q > (floor (sqrt (fromIntegral n)))) then 
                    True 
                else 
                    isPrime n (q+1)  
main = do{  putStrLn "Bir Sayı giriniz: ";
            x <- readLn;
            if (isPrime x 2) then
                putStrLn "Asal"
            else 
                putStrLn "Asal DEĞİL";
        }

Yukarıdaki Haskell kodunda 2 tane fonksiyonumuz var. isPrime fonksiyonu sayesinde kullanıcıdan aldığımız sayıyı kare köküne kadar olan bütün sayılara bölmeye çalışıyoruz. Eğer aldığımız sayı denediğimiz sayılardan herhangi birine bölünürse, sayı asal olmadığı için fonksiyonumuzun değeri False oluyor. Eğer sayı kendi kare köküne kadar olan hiç bir sayıya bölünmezse sayımız asal olduğu için fonksiyonumuzun değeri True oluyor. Farkettiyseniz isPrime fonksiyonundan main fonksiyonuna değer dönerken 'return' tarzı bir bildiri kullanmadım. Matematikteki fonksiyonları düşünün f(x)=x+3 dediğimiz zaman aslında bu fonksiyon bize birşey dönmez değilmi. Oda aslında bir sayıdır tek farkı değerinin bizim verdiğimiz x'e bağlı olması. Tabiki bu Haskell'de 'return' yok anlamına gelmiyor. İlerleyen zamanlarda bu konuya daha derinlemesine bakma şansı bulucaz. Şimdi aynı koda birde C'de bakalım:

asal.c
#include 
#include 
int isPrime(int n){
    int i;
    for(i=2 ; i<=sqrt(n) ; i++)
        if( n % i == 0)
            return 0;
    return 1;
}
int main(){
    int n;
    printf("Bir sayi giriniz:");
    scanf("%d",&n);
    if( isPrime( n ) )
        printf("Asal\n");
    else
        printf("Asal DEGIL\n");
    return 0;
}

       Fonksiyonel programlama dillerinde değişken tanımlama olmadığından  durum diye bir olgu yoktur. Mesela yukaridaki C kodunda isPrime fonksiyonunda tanımlanmış olan 'i' değişkeni o C fonksiyonu için bir durum göstergesidir. 'i' her değiştiğinde fonksiyonun durumuda değişir.
       C'deki isPrime fonksiyonundaki bir farklılıkta tam sayı (integer) 'i' değişkeniyle 'sqrt' fonksiyonunun döndüğü ondalıklı(float) sayı arasında bir kıyaslama  yapabilmemiz. Haskell'de farkedeceğiniz üzere aşşağıdaki kodu kullanarak kıyaslamadan önce sayımızı tamsayıya çevirdik.

(floor (sqrt (fromIntegral n)))

Tam sayıya çevirmeseydik kıyaslama işlemini gerçekleştirmezdik çünkü Haskell "strongly typed" bir dildir. Yani her hangi bir işlem yaparken kullandığınız değişkenlerin tipleri aynı olmak zorundadır.
        Son olarak yukardakiyne aynı işi yapan bir Haskell kodu(kodu ilerleyen zamanlarda açıklayacağım):

main = do { putStrLn "Bir sayı giriniz:";
            x <- readLn;
            let dividerList = [n| n<-[2..x-1], x `mod` n == 0] 
            in if (length dividerList)==0 then
                    putStrLn "Asal"
                else
                    putStrLn "Asal değil"        
        }




Bol matematik, biraz programlama

     En başta işin biraz matematik kısmına girip "Nedir şu fonksiyon?" sorusuna cevap vermek istiyorum. Bu kısımda programlamayla ilgili pek birşey anlatmayacağım için sıkıcı olabilir.     



       Lisede matematikte gördüğmüz fonksiyonlar aslında fonksiyonel programlamanın temelleridir. Biraz özet geçmek gerekirse, fonksiyon kavramını anlamak için önce küme kavramını anlamak gerekir. Küme dediğimiz kavram aslında objeler topluluğudur. Örnek olarak; bilgisayarınızda yüklü yazılımlar yada şu anda bu yazıyı okurken tarayıcınızda açık olan sekmeler olabilir. Küme kavramı beraberinde alt küme kavramınıda getirir, alt küme dediğimiz kavram ise yine bir kümedir. Bir alt kümenin sahip olduğu bütün elemanlar aslında onun altında olduğu kümenin elemanlarıdır. Örnek olarak; bilgisayarınızda yüklü olan oyunlar, bilgisayarınızda yüklü olan yazılımların bir alt kümesidir yada en basitinden doğal sayılar kümesi reel sayılar kümesinin bir alt kümesidir. Kümeler arasında kartezyen çarpım denilen bir işlem tanımlanmıştır bu işlemin amacı iki kümenin elemanlarından yeni bir küme oluşturmak gibi düşünülebilir. Örnek vermek gerekirse:

 A = {1,2},  B = {3,4} olsun  AxB = {(1,3),(1,4),(2,3),(2,4)} olur.

Burda tahmin edebileceğniz gibi 'x' karteztyen çarpma işleminin operatörüdür.
Bir kartezyen çarpmanın sonucunda ortaya çıkan kümenin alt kümelerine bağıntı deriz. Yine yukardaki örneğe ek olarak:

R = {(x,y) | x ve y tek sayı , x \in \!\, A  ve y \in \!\, B} olsun

bu durumda R = {(1,3)} bir bağıntıdır.


Gördüğünüz gibi R, AxB'nin bir alt kümesidir ve aynı zamanda R bir bağıntıdır.
Son olarak tanılamamız gereken şey tabiki fonksiyon. Fonksiyonlardan bahsederken tanım kümesi ve değer kümesinden bahsetmeden olmaz. Tanım kümesi kısaca bir bağıntının elemanlarında ilk elemanın ait olduğu kümedir, değer kümesi ise ikinci elemanının ait olduğu kümedir. Örnek vermek gerekirse:

 AxB = {(1,3),(1,4),(2,3),(2,4)} ise A tanım, B değer kümesidir.


Fonksiyon dediğimiz kavramda aslında bir bağıntıdır ve aşşağıdaki şartı sağlıyan her bağıntı bir fonksiyondur:

  • Tanım kümesindeki her eleman, bağıntının sadece ve kesinlikle bir elemanında bulunmalıdır.

Bu tanımı daha matematiksel yapmak istersek:

 A ve B iki küme olsun ve R AxB 'nin bir alt kümesi olsun.

 Eğer  \forall \!\, x \in \!\, A için yanlızca bir y \in \!\, B (x,y) \in \!\, R ilişkisini

 sağlıyorsa ise R bir fonksiyondur.

 Son bir örnekten sonra bu kısmı bitiricem ve fonksiyonlarla ilgili geri kalan matematiksel bilgilere haskelle haşır neşir olurken değinmeye çalışıcam. 

f : \mathbb{R} \!\, => \mathbb{R} \!\,, f(x) = x2 olsun

Burada f fonksiyonu aslında \mathbb{R} \!\,x\mathbb{R} \!\,'ın bir alt kümesidir,

f = {(x,x2) | x \in \!\, \mathbb{R} \!\,} yani

f = {...,(0,0),(1,1),(2,4),(3,9),(4,16),...}




        Şimdi yukarıda verdiğimiz örnek olan f(x) = x2  fonksiyonun Haskell koduna bakalım,

f n = n*n

Gördüğnüz gibi göz yaşartıcı derecede yalın, bir kare fonksiyonumuz oldu.


Başlangıç

        Fonksiyonel programlama, uzun zamandır üzerinde gerçekten birşeyler yapmak istediğim bir alandı. Bende içimdeki istek doğrultusunda Haskell öğrenmeye karar verdim. Öğrendikçe farkettimki bu iş hiç C veya Java gibi emir stili(imperative style) programlamaya benzemiyor. Programlama konusunda az çok tecrübem olmasına rağmen Haskell'e geçince sudan çıkmış balığa döndüğmü itiraf etmeliyim. En başlarda Haskell kodları gözüme çok anlamsız geliyordu, kısacık kodun benim C'de yapmak için baya kod yazdığım işi nasıl yapabildiğini bi türlü anlayamıyordum.  Fakat Haskelle zaman geçirdikce fark ettim ki fonksiyonel programlama gerçekten çok farklı, çok güçlü ve herşeyin ötesinde çok zevkli ve bence programlamayla uğraşan her insanın hakkında bilgi sahibi olması gereken bir konu. Bu nedenlerle, benim gibi fonksiyonel programlamayı merak eden ve bu farklı dünyaya adım atmak isteyen insanlarla öğrendiklerimi paylaşmak için bir şeyler yazmaya karar verdim. Bende yazdıkça daha çok şey öğrene bileceğimi umuyorum.
Not: Yazılarımın çoğunda okuyucunun programlama bildiğini varsayarak anlatacağım.