Haskell Notları – 1 (Değişkenler ve Fonksiyonlar)

Haskell üzerine fazla Türkçe kaynak olmamasından ve en iyi öğrenme biçiminin de öğrenirken not tutmaktan geçmesi beni bu yazı dizisini başlatmaya iten sebepler. Haskell, fonksiyonel programlama geçmişi olmayanlar için oldukça oldukça ilginç ve zor kavranan bir dil. Dolayısıyla temel kavramları Türkçe bir kaynaktan öğrenmek sizler için de öğrenim hızını birazcık da olsa arttıracaktır. Bu yazılar programlamaya yeni başlayanlar için değil, daha önceden programlamayla ilgilenmiş fakat fonksiyonel programlamaya tamamen/kısmen yeni olan kişiler için. Bu bilgiyi de verdikten sonra kurulum işlemleri ile başlayalım:

Kurulum

Haskell’in bir çok derleyici implementasyonu bulunmakta. GHC bunlar arasında de facto lider konumunda ve ben de onu kullanacağım. Bu derleyici dertsiz tasasız kullanmanın en güzel yolu ise Haskell Platform. GHC hiçbir ekstra gereksinime ihtiyaç duymayan çalıştırılabilir dosyalar üretebiliyor. Oldukça yenetekli bir derleyici olan ghc’nin aynı zamanda interaktif bir arayüzü de bulunmakta. Sürekli derle/çalıştır yapmak yerine bu arayüz üzerinden çalışacağız.

Terminale ghci komutunu girerek interaktif arayüzümüze ulaşıyoruz. :? komutu ile ghci üzerinde kullanabileceğiniz diğer komutları görebilirsiniz. Şu an için bilmeniz gereken en önemli komut :q, çıkış yapmanızı sağlıyor.

Kaynaklar

Bu yazıyı yazarken kullandığım kaynaklar şöyle, burada genel bilgiyi aldıktan sonra daha ayrıntılı inceleme için bu kaynaklara bakabilirsiniz:

  • http://learnyouahaskell.com
  • http://realworldhaskell.org
  • https://en.wikibooks.org/wiki/Haskell

Başlangıç

ghci’yi çalıştırdıktan sonra beni şöyle bir şey karşılıyor ve muhtemelen sizi de:

GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Prelude>

Artık komutlarımızı buraya yazacağız. Klasik başlangıcımızı yapalım:

Prelude> 3 + 5
8
Prelude> 22 / 7
3.142857142857143
Prelude> 3 * 2
6

Neler döndüğü oldukça açık gibi. Biraz da şunlara bakalım:

Prelude> True == True
True
Prelude> "Haskell" == "haskell"
False
Prelude> 3 /= 4
True

Burada da her şey tanıdık gelmiş olmalı. Tek farklı olabilecek notasyon /=. Diğer dillerden alışık olduğunuz != operatörü ile aynı şey aslında. Matematikten aşina olduğumuz eşit değil (≠) işareti ile olan benzerliği fark etmişsinizdir. Matematik ile bir sürü anoloji yapacağız ilerde, hazırlıklı olun.
Değişkenler

Öcelikle Haskell’de değişken diye bir şey yok (Fonksiyonlara gönderilen parametreler lambda calculus’deki değişken tanımına uyar fakat konumuz bu değil). Peki biz nasıl programlama yapacağız? Önce gelin C benzeri dillerdeki bir değişken tanımına bakalım:

int main() {
    int i;  // i diye bir değişken tanımladık
    i = 2;  // i'nin tuttuğu değeri 2 yaptık.
    i = 5;  // i'nin değerini 5 olarak değiştirdik.
}

Şimdi Haskell’de buna benzer bir şey yapmaya çalışalım:

a = 2
a = 5

Bunun sonucunda alacağınız hata şudur:

Multiple declarations of 'a'.

Hatanın size demek istediği şey a’yı bir kere tanımladığınızda bir daha değiştiremeyeceğiniz. Matematiğe dönelim yeniden, bir problem içerisinde Pi diye bir değer olduğunu düşünelim. Aynı problem içinde Pi’in değeri değişebilir mi? Hayır. Aynı mantık Haskell’de de bulunmakta. Bir bakıma Haskell’in değişkenleri için immutable diyebiliriz. Ama bundan daha iyi bir tanım var:
Fonksiyonlar

Haskell’in bize sunduğu hazır fonksiyonlardan iki tanesi şöyle:

Prelude> succ 3
4
Prelude> pred 4
3
Prelude> pred 'b'
'a'

succ fonksiyonu aldığı parametrenin tanımlanmış varisini döndüren bir fonksiyon, successor’ün kısaltması. pred ise ise kendinden önce geleni döndüren bir fonksiyon. Şimdi notasyona bir bakalım. Fonksiyona parametre gönderirken parametre ayracı görevini ” “(boşluk) görüyor. yani succ yazdıktan sonra bir boşluk bırakıp parametremizi gönderiyoruz.

Prelude> succ 3 * 5
20

Burada olan şey ise şu. succ fonksiyonu kendisine parametre olarak gelen 3’ü alıyor ve varisini bulup döndürüyor. Yani artık elimizde 4 * 5 var ve sonuç 20 olarak hesaplanıyor. Peki 3 * 5’in yani 15’in varisini bulmak isteseydik ne yapacaktık?

Prelude> succ (3 * 5)
16

Şimdi de mod fonksiyonuna bakalım, her şey daha açık olacaktır. mod fonksiyonu iki parametre alır, bir sayı ve bölen sayı, sonuç olarak da kalanı döndürür.

Prelude> mod 15 2
1

Bunun imperatif bir dildeki karşılığını mod(15, 2) olarak düşünebilirsiniz. Haskell’de yazdığımız pred (mod (3 * 5) 2) fonksiyonun karşılığı ise pred(mod(3*5, 2)) olacaktır. Aynı şekilde foo 3 (baz “aha”) ise foo(3, baz(“aha”)) olacaktır.

Gördüğünüz gibi fonksiyonlar prefix gösterim biçiminde. Fakat mod işlemini mod 5 2 şeklinde yazmak çok okunaklı olmayabilir, infix gösterim biçimiyle daha anlamlı hale gelebilir. Fonksiyonları infix biçimde yazmak için yapmanız gereken şey, fonksiyonu ` işaretleri arasına almak. Yani:

Prelude> mod 15 2
1
Prelude> 15 `mod` 2
1

Şimdi biz Haskell’de bir fonksiyon tanımlayalım:

a = 2

Herhangi bir parametre almayan ve sonuç olarak 2 değerini döndüren bir fonksiyon. Yukarıda değişken tanımlamaya çalışırken de aynısı yapmıştık ve bunun aslında bir fonksiyon olduğunu gördük. Peki ta parametre alan bir fonksiyon nasıl olurdu?

triple x = x * 3

Burada durup sizden yeni bir dosya oluşturmanızı ve adını first.hs (ya da içinizden ne geliyorsa) koymanızı istiyorum. Şimdi dosyayı açıp içine yukarıdaki fonksiyon tanımımızı yazıp kaydedebiliriz. Daha sonra bu dosyanın bulunduğu dizinde terminali açıp ghci’yi çalıştırın.

Prelude> :load first.hs
[1 of 1] Compiling Main             ( first.hs, interpreted )
Ok, modules loaded: Main.
*Main>

:load komutu ile ghci’nin first.hs dosyasını yüklemesini sağladık. Artık bu dosya içinde bulunan fonksiyonları rahatlıkla çağırabileceğiz.

*Main> triple 4
12
*Main> triple (triple 4)
36

Yeniden dosyayı açıp içine şu fonksiyonları ekleyebiliriz:

areaCircle r = pi * r * r
areaRect w h = w * h

Yeni fonksiyonları ghci’de görebilmek adına dosyayı yeniden yüklememiz gerekli, bunun için :reload komutunu veriyoruz. Bu arada :load’ın kısaltması :l, :reload‘ın kısaltması ise :r.

*Main> :reload
[1 of 1] Compiling Main             ( first.hs, interpreted )
Ok, modules loaded: Main.
*Main> areaCircle 3
28.274333882308138
*Main> areaCircle 3 - areaRect 4 5
8.27433388230813

İki çemberin alanları farkını bulan bir fonksiyon yazmak isteseydik şunu yapabilirdik:

diffCircles r1 r2 = areaCircle r1 - areaCircle r2

areaCircle fonksiyonunu programın başka bir yerinde kullanmıyorsak açık bir tanım yapmamıza gerek yok, o yüzden diffCircles fonksiyonunu şu şekilde yazabiliriz:

diffCircles r1 r2 = (pi * r1 * r1) - (pi * r2 * r2)

Fakat burada da gördüğünüz üzere tekrara düştük ve hoş bir kod olmadı. Yardımımıza ise where ifadesi koşuyor bu konumda. where sayesinde yerel tanımlamalar yapabiliyoruz, şöyle ki:

diffCircles r1 r2 = area r1 - area r2 where area r = pi * r * r

Aynı zamanda bu fonksiyonu daha okunaklı yazabiliriz:

diffCircles r1 r2 = area r1 - area r2 
        where area r = pi * r * r

Bunu da dosyamıza kaydedelim ve:

*Main> :r
[1 of 1] Compiling Main             ( first.hs, interpreted )
Ok, modules loaded: Main.
*Main> diffCircles 4 2
37.69911184307752

İkinci dereceden bir denklemin köklerini bulan bir fonksiyonu yazmaya çalışalım şimdi: ax^2+bx+c=0 denklemimiz olsun. Denklemin diskriminantı ise \Delta = b^2 - 4ac . Diskriminantın pozitif olduğu durumlarda ise denklemin kökleri \frac{-b + \sqrt {\Delta}}{2a} \quad\text{ve}\quad \frac{-b - \sqrt {\Delta}}{2a}, . Bu durumda denklemin köklerini döndüren fonksiyonumuz şu şekilde olacak:

roots a b c = [root1, root2]
                where
                    delta = (b * b) - (4 * a * c)
                    root1 = (-b - sqrt delta) / (2 * a)
                    root2 = (-b + sqrt delta) / (2 * a)

Bunu dosyamıza kaydedip ghci’de yeniden yüklediğimiz zaman:

*Main> :r
[1 of 1] Compiling Main             ( first.hs, interpreted )
Ok, modules loaded: Main.
*Main> roots 1 (-3) 2
[1.0,2.0]

Fonksiyon görüldüğü üzere üç parametre alıyor. where ifadesinin yardımıyla yerel tanımlamalar ile iki kökü hesaplıyor ve daha sonra bu kökleri bir listenin içine sırayla koyup döndürüyor. Bir sonraki yazıda da listelerden uzun uzun bahsedeceğim.

One Comment

Leave a Reply