FONKSİYONEL PROGRAMLAMANIN TEMELLERİ
Programlamanın davranışını anlamanın iki ana yolu vardır. İlki ve tarihsel olarak daha yaygın olanı: Programcının bilgisayara belirli bir şekilde davranması için bir dizi talimatlar sağladığı görünüm şeklidir. Programlamanın bu modeli, programcıyı belirli bir araç tasarımına yani bir bilgisayara bağlar. Bu tip programlamada, bilgisayar: Girdi alan, belleğe erişen, bir işlem ünitesine talimatlar gönderen ve son olarak kullanıcıya çıktı sağlayan bir cihazdır. Böyle bir bilgisayar modeli, ünlü matematikçi ve fizikçi “Von Neumann”dan sonra, “Von Neumann mimarisi” olarak adlandırılmıştır.
Programlar hakkındaki bu düşünce şeklini kendisinde en iyi barındıran programlama dili, C programlama dilidir. Bir C programı, işletim sistemi tarafından kontrol edilen standart girdideki veriyi alır, depolar ve sık sık manuel olarak yönetilmesi gereken fiziksel bellekteki gerekli değerleri elde eder, işaretçilerin(pointers) belirli bir bellek bloğuna işlenmesini gerektirir ve son olarak işletim sistemi tarafından kontrol edilen standart çıktı üzerinden tüm cıktıyı dönderir. C programları yazarken, programcılar önündeki bilgisayarın fiziksel mimarisi kadar eldeki sorun hakkında da çok şey anlamalıdırlar.
Fakat Von Neumann mimarisiyle bütünleşen bir bilgisayar modeli, hesaplamalar yapmanın tek yolu değildir. İnsanlar, bellek ayırma ve talimat setlerini düşünmekle ilgisi olmayan çok çeşitli hesaplamalar da yaparlar: Bir raftaki kitapları sıralama, matematikte bir fonksiyonun türevini alma, arkadaşlara talimatlar verme ve benzerleri gibi. C kodu yazarken, bilgisayarın belirli bir uygulamasını programlıyoruz. Fortran'ı kuran takıma liderlik eden John Backus, Turing Ödülü adlı dersinde “programlama Neumann modelinden koparılabilir mi?” diye sormuştu.
İşte bu soru da, bu kitaptaki ilk ünitenin konusu olan programlamayı anlamanın ikinci şekline yani Fonksiyonel Programlama'ya öncülük ediyor. Fonksiyonel programlama, Neumann tarzındaki programlamayı özgürleştirmeye çalışır. Fonksiyonel programlamanın temelleri, belirli bir uygulamayı aşan soyut, matematiksel hesaplama kavramlarıdır. Bu, genellikle onları açıklayarak problemleri basitçe çözen bir programlama metoduna öncülük eder. Fonksiyonel programlama, bilgisayarlara değil hesaplamalara odaklanarak programcının birçok zorlu sorunu çözmeyi çok daha kolay hale getirebilen güçlü soyutlamalara erişimini sağlar.
Bunun bedeli ise o işe başlama işinin çok daha zor olabilmesidir. Fonksiyonel programlamada düşünceler genellikle soyuttur ve ilk prensiplerdeki programlama fikrini oluşturarak başlamak zorundayız. Yararlı programlar oluşturabilmeden önce, birçok kavramın öğrenilmesi gerekir. Kitapta bu ilk ünite boyunca çalışirken, bir bilgisayar programlamanın da ötesindeki bir şekilde programlamayı öğreniyor olduğunuzu unutmayın.
C programlama dili, Von Neumann tarzı programlamanın neredeyse harika bir şekliyken; Haskell ise öğrenebileceğiniz en saf fonksiyonel programlama dilidir. Bir dil olarak Haskell Backus'un hayalini tam olarak karşılar ve daha bilindik programlama tarzlarına geri dönmenize izin vermez. Bu durum; Haskell'in öğrenimini diğer birçok programlama dilinden daha zor yapar fakat Haskell'i öğrenmede ilerledikçe, Fonksiyonel Programlama konusunda derin bir fikir edinmemeniz de imkansız hale gelir. Bu ünitenin sonunda; Haskell'i öğrenme yolculuğunuza hazırlanmanın yanı sıra tüm diğer fonksiyonel programlama dillerinin temellerini de anlamak için, fonksiyonel programlama konusunda yeterince güçlü bir temele sahip olacaksınız.
DERS 2: FONKSİYONLAR VE FONKSİYONEL PROGRAMLAMA
Ders 2'yi okuduktan sonra;
• Fonksiyonel programlamanın genel düşüncesini anlayabilecek,
• Haskell'de basit fonksiyonlar tanımlayabilecek,
• Haskell'de değişkenler bildirebilecek ve,
• Fonksiyonel programlamanın yararlarını açıklayabileceksiniz.
Haskell'i öğrenirken anlamanız gereken ilk başlık, fonksiyonel programlamanın ne olduğudur. Fonksiyonel programlama, ustalaşmak için zorlayıcı bir başlığı olan bir üne sahiptir. Bu şüphesiz doğru olmasına rağmen, fonksiyonel programlamanın temelleri ilginç bir şekilde açık ve anlaşılırdır. Öğrenmeniz gereken ilk şey, fonksiyonel bir programlama dilinde bir fonksiyonun olması ya da bulunmasıdır. Büyük ihtimalle, bir fonksiyon kullanmanın ne anlama geldiğine dair zaten iyi bir fikriniz vardır. Bu derste, fonksiyonların Haskell'de sadece kodunuzu daha kolay hale getirmekle kalmayıp aynı zamanda programlama hakkında tamamen yeni düşünme yollarına öncülük edeceği temel ve basit kuralları göreceksiniz.
Şuna Bir Gözatın: Siz ve arkadaşlarınız pizza alıyorsunuz. Menüde üç faklı ücreti ile üç farklı boyutta pizza var:
• 18 inch 20 $
• 16 inch 15 $
• 12 inch 10 $
Hangi seçimin paranız için size en iyi pizzayı vereceğini bilmek istiyorsunuz. Pizzanin kare başına dolarlik maliyetini verecek bir fonksiyon yazmak istiyorsunuz.
FONKSİYONLAR
Bir fonksiyon tam olarak nedir? Bu soru, fonksiyonel programlamayı keşfedip keşfetmeyeceğinizi sormak ve anlamak için önemli bir sorudur. Haskell'de fonksiyonların davranışı direkt olarak matematikten gelir. Matematikte sıklıkla, bir “x” parametresi alan ve bir “y” değeriyle eşleşen bazı “f” fonksiyonlarının olduğu anlamına gelen “ F(x) = y” gibi şeyler söyleriz. Matematikte her “x” paremetresi tek bir şeyle ve sadece tek bir tane “y” değeriyle eşleşebilir. Eğer verilen bir “f” fonksiyonu için eşitlik “ F(2) = 2, 000, 000 “ şeklindeyse, o fonksiyon asla “ F(2) = 2, 000, 001 “ durumunda olamaz.
Düşünceli bir okur şunu sorabilir: Karekök alma fonksiyonu nedir? 4'ün iki kökü var; 2 ve -2 yani açıkçası iki adet “y” ye işaret ettiğinde karekökü alınan “x” nasıl gerçek bir fonksiyon olabilir? Farkına varılacak ana şey ise şudur: “x” ve “y” aynı şey olmak zorunda değildir. Sqrt X'in yani X'in karekökünün pozitif bir kök olduğunu söyleyebiliriz, yani hem “x” hem de “y” pozitif reel sayılardır ki işte bu da bu sorunu çözmektedir. Fakat, pozitif bir reel sayıdan çift reel sayılara kadar bir fonksiyon olan kök X'imiz de olabilirdi. Bu durumda, her bir “x” kesinlikle tek bir çiftle eşleşir.
Haskell'de fonksiyonlar kesinlikle matematikteki gibi çalışırlar. Şekil 2.1 “simple” adlı bir fonksiyon gösterir:
Simple X = X
• Simple = fonksiyonun is midir? Haskell'de tüm fonksiyonlar küçük bir harfle başlar.
• Fonksiyon adından hemen sonra gelen “x” = fonksiyonun argümanı olup tüm fonksiyonlar en az bir argüman alır.
• En sondaki “x” = Fonksiyonun davranışı burada tanımlanır. Bu durumda sadece argümanınızı yani ilk X değerini dönderiyorsunuz.
“Simple” fonksiyonu tek bir “x” argümanı alır ve daha sonra dokunulmamış olan bu argümanı dönderir. Diğer birçok programlama dillerinden farklı olarak Haskell'de bir değer dönderiyor olduğunuzu belirtmenize gerek olmadığına dikkat edin. Haskell'de fonksiyonlar zaten bir değer Döndürmek zorundadırlar, yani bunu asla açık bir şekilde yapmak ya da belirtmek gerekmez. Şimdi “simple” adlı fonksiyonunuzu GHCi'ye yükleyebilir ve nasıl davrandığını görebilirsiniz. Bir fonksiyon yüklemek için, yapmanız gereken tüm şey; bir dosya içinde ona sahip olmak ve GHCi'de “:load <dosya_ismi> “ komutunu kullanmaktır:
GHCi> simple^2
2
GHCi> simple "dog"
"Dog"
NOT: Bu bölümde, komutları çalıştırmak ve sonuçları elde etmek için Haskell'in etkileşimli Read-Eval-Print Loop(REPL) yani oku-değerlendir-yazdır döngüsünü kullanıyoruz.
Haskell'deki tüm fonksiyonlar, onları matematikteki fonksiyonlar gibi davranmaya zorlayan üç kural izlerler:
1 - Tüm fonksiyonlar bir argüman almak zorundadırlar,
2 - Tüm fonksiyonlar bir değer Döndürmek zorundadırlar,
3 - Bir fonksiyon aynı argümanla çağrıldığında, yine aynı değeri döndermelidir.
Üçüncü kural, bir fonksiyonun temel matematiksel tanımının bir parçasıdır. Aynı argümanın her zaman aynı sonucu üretmesi gerektiği kuralı, bir programlama dilindeki fonksiyona uygulandığında; “referans şeffaflığı” olarak adlandırılır.
FONKSİYONEL PROGRAMLAMA
Eğer fonksiyonel programlama sadece bir çift X(yani X'in çoğulu olan)'lerden bir çift y(yine Y'nin çoğulu olan)'lere olan eşleştirmelerden ibaretse; o zaman, bunların programlamayla ne ilgisi var ki? 1930'larda, Alonzo Church adlı bir matematikçi sadece fonksiyonlar ve değişkenlerin(yani X'ler ve y'ler) kullanıldığı bir sistem mantığı oluşturma girişiminde bulundu. Bu sistem mantığı “lambda calculus” olarak adlandırılır. Lambda calculus modelinde; her şeyi fonksiyonlar olarak belirtirsiniz, true ve false fonksiyonlardır ve hatta tamsayılar bile fonksiyonlar olarak belirtilebilir.
Church'un hedefi: Dizi teorisinin matematiksel alanındaki birkaç sorununu çözmekti. Maalesef “lambda calculus” modeli bu sorunları çözemedi fakat Church'un çalışmasından çok daha ilginç bir şey göründü. Lamda Calculus modelinin, bir Turing makinesine eş değer olan evrensel bir hesaplama modeline izin verdiği ortaya çıktı.
NOT: Turing Makinesi Nedir? ===> Turing makinesi, ünlü bilgisayar bilimcisi Alan Turing tarafından geliştirilen soyut bir bilgisayar modelidir. Teorik bir bakış açısıyla; Turing modeli yararlıdır, çünkü sadece dijital bir bilgisayarda değil fakat herhangi bir bilgisayarda da neyin hesaplanabileceğini ve neyin hesaplanamayacağını düşünmenizi sağlar. Bu model, eğer her biri bir Turing makinesini simule edebilirse, bilgisayar bilimcilerinin hesaplama sistemleri arasındaki eşitliği görmesini de sağlar. Bunu, örneğin hem Java'da hem de Assembly dilinde hesaplayabileceğiniz hiçbir şey olmadığını göstermek için kullanabilirsiniz.
Lambda Calculus ve hesaplama arasındaki bu ilişki keşfi, “Church-Turing tezi(daha fazla bilgi için buraya bakın www.alanturing.net/turing_archive/ pages/reference%20articles/The%20Turing-Church%20Thesis.html)” olarak adlandırılır. Bu keşfe dair harika olan şey şudur ki; artık programlamada matematiksel olarak sağlam bir modeliniz var!
Kullandığınız birçok programlama dili mühendisliğin harika birer parçalarıdır fakat programların nasıl davranacağı hakkında az güvence sağlar. Matematiksel bir temel sayesinde Haskell, yazdığınız koddaki tüm bug ve hata türlerini kaldırabilir. Programlama dillerindeki en son araştırmalar, programların tam olarak beklediğiniz şeyleri matematiksel yollarla sağladığını gösteriyor. Ayrıca, çoğu programlama dili tasarımının matematiksel olmayan doğası, kullanabileceğiniz soyutlamaların dildeki mühendislik kararlarıyla sınırlandıgı anlamına gelir. Eğer matematiksel programlama yapabiliyorsanız, hem kodunuzla ilgili şeyleri geliştirebilir hem de matematiğin izin verdiği neredeyse sınırsız soyutlamalara erişim sağlayabilirdiniz. İşte bu, fonksiyonel programlamanın hedefidir: Kullanılabilir yoldan matematiğin gücünü programcıya sunmak.
PRATİKTE FONKSİYONEL PROGRAMLAMANIN DEĞERİ
Programlamada matematiksel modelin çeşitli pratik anlamları vardır. Tüm fonksiyonların değerler almak ve Döndürmek zorunda olması ve her zaman aynı argüman için aynı değeri Döndürmek zorunda olması gibi basit ya da temel kurallardan dolayı, Haskell güvenli bir programlama dilidir. Her zaman tam olarak beklediğiniz şekilde davrandıkları zaman programlar güvenlidir ve davranışları hakkında kolayca akıl yürütebilirsiniz. Güvenli bir programlama dili, programlarınızı beklendiği gibi davranmaya zorlayan bir dildir.
Hadi şimdi de güvenli olmayan ve fonksiyonlardaki basit kurallarımızı ihlal eden koda bir göz atalım. Varsayın ki, direkt yeni bir kod tabanı okuyorsunuz ve aşağıdakilere benzeyen kod satırlarıyla(fonksiyon çağrılarındaki saklı durum) karşılaşıyorsunuz:
tick()
İf(timeToReset){
reset()
}
Yukarıdaki kod açıkçası bir Haskell kodu değildir çünkü hem “tick” hem de “reset”, oluşturduğumuz kuralları ihlal eder. Fonksiyon ne herhangi bir argüman alır ne de herhangi bir değer dönderir. O zaman sorulacak soru şudur; bu fonksiyonlar ne yapıyorlar ve Haskell'de fonksiyonlardaki bu fark nasıl oluşuyor? Tahmin etmesi uzun sürmeyecektir ki;“tick” bir sayacı artırıyor ve “reset” o sayacı başlangıç değerine yeniden depoluyor. Hatta tam olarak haklı olmasak bile, bu akıl yürütme biçimi bize sorumuza ilişkin bilgi veriyor. Eğer bir fonksiyona bir argüman geçmiyorsanız ortamınızdaki bir değere erişiyor olmalısınız ve eğer bir değer döndermiyorsanız ortamınızdaki bir değeri değiştiriyor da olabilirsiniz. Programlama ortamınızdaki bir değeri değiştirdiğinizde, programın durumunu değiştiriyorsunuz. Durumu değiştirme dediğimiz olay ise, kodunuzda yan etki oluşturur ve bu yan etkiler o kod üzerinde akıl yürütmeyi zorlaştırabilir ve bu nedenle de güvensizdirler.
Büyük ihtimalle hem “tick” hem de “reset”, herhangi bir programlama dilinde kötü tasarlandığı düşünülen global bir değişkene(yani programdaki herhangi bir yerden ulaşılabilir olan bir değişkene) erişiyorlar. Fakat yan etkiler en basit, iyi yazılmış bir kod hakkında bile akıl yürütmeyi zorlaştırır. Bunu görmek için, “myList” adlı bir değerler dizisine bakacağız ve yerleşik işlevselliği kullanarak onu ters çevireceğiz. Aşağıdaki kod(standart kitaplıklardaki kafa karıştırıcı davranış) Python, Ruby ve JavaScript'da geçerlidir; eğer ne yaptığını anlayabilirseniz bakın:
MyList = [1,2,3]
myList.reverse()
NewList = myList.reverse()
Şimdi “newList” adlı değişkenin değerinin ne olmasını bekliyorsunuz? Bu, Python, Ruby ve JavaScript'ta geçerli bir kod olduğu için; newList adlı değişkenin değerinin aynı olması gerektiğini zannetmek mantıklı görünüyor. Bu üç dil için yukarıdaki kodun çıktıları ise aşağıdaki gibidir:
Ruby -> [3,2,1]
Python -> None (hiçbir şey)
JavaScript -> [1,2,3]
Üç dilde tıpı tıpına aynı kod için tamamen farklı üç cevap veya çıktı! Python ve Javascrip'ın her ikisi için de; “reverse) adlı fonksiyon çağırıldığında meydana gelen yan etkiler mevcuttur. “reverse” fonksiyonunu çağırmanın yan etkisi her dilde farklı ve programcıya görünür olmadığı için, her iki dil de farklı çıktılar üretiyor. Burada Ruby kodu, yan etkiler olmadan Haskell gibi davranıyor. İşte burada “referans şeffaflığı” adlı kavramın değerini görüyorsunuz. Haskell ile, herbir fonksiyonun hangi yan etkilere sahip olduğunu her zaman görebilirsiniz. İlk başta “tick” ve “reset” i çağırdığınızda, yapılan değişiklikler size görünmedi. Kaynak koda bakmaksızın, hangi değerleri hatta kaç tanesini kullanıyor ve değiştiriyor olduğunuzu kesinlikle bilme imkanınız yoktur. Haskell, fonksiyonların yan etkilere sahip olmasına izin vermez ki bu da tüm Haskell fonksiyonlarının neden bir argüman alıp bir değer döndermesi gerektiğini açıklar. Eğer Haskell fonksiyonları her zaman tek değer döndermeseydi, bu sefer gizli olana erişmeleri gerekirdi, bu da artık şeffaf olmadıkları anlamına gelirdi.
Haskell fonksiyonlarının bu küçük özelliği, önemli ölçüde tahmin etmesi daha kolay olan koda öncülük eder. Ruby'de bile programcının yan etkileri kullanmasına izin verilir. Diğer programcının kodunu kullanırken, bir fonksiyon veya metot çağırdığınızda ne olacağı hakkında hala hiçbir varsayımda bulunamazsınız. İşte Haskell buna izin vermediği için, herhangi bir programcı tarafından yazılan herhangi bir coda bakabilir ve davranışı hakkında akıl yürütebilirsiniz.
NOT: Birçok programlama dili, bir değeri artırmak için “++” işlecini kullanır. Örnegin: X++, X'in değerini artırır. Sizce Haskell'in bu şekilde çalışan bir operatörü(işleci) veya fonksiyonu var mıdır? Fonksiyonlardaki matematiksel kurallarımızı çiğneyeceği için, C gibi dillerde kullanılan “++” operatörü Haskell'de bulunamaz. En açık kural şudur ki; her zaman bir değişkende “++” operatörünü çağırırsınız fakat sonuç farklıdır.
1-Değişkenler
Haskell'de değişkenler açık ve anlaşılırdır. Aşağıdaki kodda (ilk değişkeninizi tanımlama), 2'yi X adlı değişkene atıyorsunuz:
X = 2
Haskell'deki değişkenlerle ilgili tek tuzak, onların gerçekte asla bir değişken olmamalarıdır. Eğer aşağıdaki Haskell kod parçasını derlemeye çalışmış olsaydınız, sonraki listede gösterildiği gibi bir hata alırdınız. Değişkenler, değişken değildir yani bir kere tanımlandı mı artık daha değişemezler. Örneğin aşağıdaki kodlarda, ikinci satır X'in ilk değerini değiştirdiği için derlemeyecektir:
X = 2
X = 3 (ilk değer 3'e değiştiği için derlemeyecektir)
Haskell'de değişkenler üzerine düşünmenin en iyi yolu onları “tanımlamalar” olarak düşünmektir. Bir kez daha görüyoruz ki; matematiksel düşünme, tipik olarak kodla ilgili düşünme şeklinizin yerini alıyor. Sorun şudur ki; birçok programlama dilinde yeniden değişken atama, birçok sorunu çözmek için gereklidir. Değişkenleri değiştirememe imkansızlığı, ayrıca “referans şeffaflığı” kavramıyla da ilgilidir. Bu uyulması gereken katı bir kural gibi görünebilir fakat faydası şudur ki; her zaman bir fonksiyon çağırdıktan sonra tıpatıp aynı kalacağını bilirsiniz.
NOT: Bir “++” operatörü olmayan diller bile sıklıkla bir değeri artırmak için de kullanılan “+=” operatörüne izin verirler. Örneğin “ X += 2 “, X değişkenini 2 ile artırır. “ += “ i kurallarımıza uyan bir fonksiyon olarak düşünebilirsiniz: Bir değer alır ve bir değer dönderir. Peki bu, “ += “ operatörünün Haskell'de var olabileceği anlamına mı gelir? “+=” operatörü, tıpkı “++” operatöründe olduğu gibi, bir argüman alıp döndermesine rağmen; Haskell'de de her zaman “ += “ operatörünü çağıtabilirsiniz, farklı bir sonuç elde edersiniz.
Programlamada değişkenlerin ana faydası, kodunuzu açıklığa kavuşturmak ve tekrardan kaçınmaktır. Örneğin “ calcChange “ adlı bir fonksiyon istediğinizi düşünün. Bu fonksiyon iki argüman alır: Borçlu olunan(owed) ve ödenen(given) ne kadar. Eğer size yeterli para veriliyorsa, farkı bulun ya da dönderin. Fakat size yeterli para verilmiyorsa, zarara sokan dolarlar vermek istemezsiniz: Yani çıktı olarak 0(sıfır) döndereceksiniz. İşte bunu yazmanın bir yolu:
CalcChange owed given = if given - owed > 0
Then given - owed
Else 0
Bu fonksiyonda iki şey hatalıdır:
• Küçücük bir fonksiyonun bile okuması zordur. Her seferinde ne olduğuna dair akıl yürütmek zorunda kaldığınız “ given - owed “ ifadesini görüyorsunuz. Her ne pahasına olursa olsun, çıkarma işleminden daha karmaşık bir şey için bu hoş olmayacaktır.
• Hesaplamanızı tekrarlıyorsunuz! Çıkartma işlemi basit bir işlemdir fakat eğer bu işlem daha maliyetli bir işlem olmuş olsaydı, gereksiz bir şekilde kaynak tüketiyor olurdunuz.
Haskell bu sorunları özel bir “ where “ yantümcesini kullanarak çözer. İşte bir önceki fonksiyonun “ where “ ifadesiyle yazılmış şekli:
CalcChange owed given = if change > 0
Then change
Else 0
Where change = given owed (burada “given - owed” farkı bir kere hesaplanıyor ve Change'e atanıyor)
İlginç olarak dikkatinizi çekmesi gereken ilk şey; “where” ifadesinin değişkenleri yazmak için kullanılan normal sırayı tersine çeviriyor olmasıdır. Çoğu programlama dilinde, değişkenler kullanılmadan önce bildirilirler. Çoğu programlama dilindeki bu gelenek kısmen, durumu değiştirebilmenin yan ürünü yani farklı bir şeklidir. Değişken sırası önem taşır, çünkü her zaman bir şeyin değerini atadıktan sonra yeniden atayabilirsiniz. Haskell'de referans şeffaflığından dolayı, bu bir sorun teşkil etmiyor. Haskell yaklaşımı ile birlikte bir okunabilirlik kazancı da var: Eğer algoritmayı okursanız, amaç gayet açıktır.
NOT: Aşağıdaki where ifadesinin eksik kısmını tamamlayınız:
DoublePlusTwo X = doubleX + 2
Where doubleX = __________
Yukarıdaki sorunun cevabı aşağıdaki gibi olmalıydı:
DoublePlusTwo X = doubleX + 2
Where doubleX = x*2
2 - Değişken Olan Değişkenler
Değişiklik hayatın kaçınılmaz bir parçası olduğu için, bazen yeniden atanabilir değişkenlere sahip olmak mantıklı hale gelir. Bu hallerden biri, Haskell REPL, GHCi'da çalışırken meydana gelir. GHCi'da çalışırken, değişkenleri yeniden atamanıza izin verilir. İşte bir örnek:
GHCi> X = 7
GHCi> X
7
GHCi> X = [1,2,3]
GHCi> X
[1,2,3]
GHC'nin 8.sürümünden önce, Haskell'de onları diğer değişkenlerden farklı olarak işaretlemek için, GHCi'de değişkenlerin “ let “ anahtar kelimesi ile başlatılması gerekiyordu. Eğer isterseniz GHCi'de hala değişkenleri “let” anahtar kelimesini kullanarak tanımlayabilirsiniz:
GHCi> let X = 7
GHCi> X
7
Tek satır fonksiyonların aynı şekilde tanımlanabileceğini de belirtmek gerekir:
GHCi> let F X = x^2
GHCi> F 8
64
Haskell'de diğer birkaç özel durumda, bu şekilde kullanılan “ let “ anahtar kelimesini göreceksiniz. Kafa karıştırıcı olabilir fakat bu farklılık öncelikli olarak gerçek dünyadaki işleri daha az sinirbozucu hale getirmek içindir. GHCi'de değişkenlerin tanımlamalarını değiştirebilmenin özel bir durum olduğunu kabullenmek önemlidir. Haskell katı olabilmesine rağmen, farklı bir değişkenle deneyimlemek istediğiniz her seferinde, GHCi'yi yeniden başlatmak zorunda kalmak sinir bozucu olurdu.
NOT: Aşağıdaki kodda X adlı değişkenin son değeri nedir?
GHCi> let X = simple simple
GHCi> let X = 6
Değerleri yeniden atayabileceğiniz için, X değişkeninin son değeri 6'dır.
ÖZET
Bu derste amacımız, sizi fonksiyonel programlama ile tanıştırmak ve Haskell'de basit fonksiyonlar yazmaktı. Fonksiyonel programlamanın bir fonksiyonun davranışı üzerinde sınırlar ya da kısıtlamalar koyduğunu gördünüz. Bu kısıtlamalar aşağıdaki gibidir:
• Bir fonksiyon her zaman bir argüman almak zorundadır
• Bir fonksiyon her zaman bir değer Döndürmek zorundadır
• Aynı argümanla aynı fonksiyonu çağırmak, her zaman aynı sonucu Döndürmek zorundadır.
Bu üç kuralın Haskell'de program yazma şekliniz üzerinde derin sonuçları vardır. Bu tarzda kod yazmanın başlıca yararı şudur ki; programlarınız ve tahmin edildiği gibi onların davranışları hakkında akıl yürütmesi çok daha kolaydır. Hadi bunu anlayıp anlamadığınızı görelim:
Soru - 1: Haskell'in “ if then else “ ifadesini “ calcChange “ adlı fonksiyonu yazmak için kullandınız. Haskell'de tüm “ if “ ifadeleri bir “ else “ bileşeni içermek zorundadır. Fonksiyonlar için verilen üç kuralımız göz önüne alındığında, neden tek başına bir “ if “ ifadeniz olamıyor?
Soru - 2: Sırasıyla “ n “ adlı bir argümanı artıran(increment), çarpan(double) ve karesini alan(square); “ inc”, “ double “ ve “square” adlı fonksiyonlar yazınız.
Soru - 3: “ n “ değeri alan bir fonksiyon yazın. Eğer “ n “ çift bir sayıysa fonksiyon “n-2” yi, tek sayıysa da 3X(n-1)'in değerini döndürsün. Sayının çift olup olmadığını kontrol etmek için, Haskell'in ya “ even “ ya da “ mod(Haskell'in modül fonksiyonu) “ fonksiyonunu kullanabilirsiniz.
Programlamanın davranışını anlamanın iki ana yolu vardır. İlki ve tarihsel olarak daha yaygın olanı: Programcının bilgisayara belirli bir şekilde davranması için bir dizi talimatlar sağladığı görünüm şeklidir. Programlamanın bu modeli, programcıyı belirli bir araç tasarımına yani bir bilgisayara bağlar. Bu tip programlamada, bilgisayar: Girdi alan, belleğe erişen, bir işlem ünitesine talimatlar gönderen ve son olarak kullanıcıya çıktı sağlayan bir cihazdır. Böyle bir bilgisayar modeli, ünlü matematikçi ve fizikçi “Von Neumann”dan sonra, “Von Neumann mimarisi” olarak adlandırılmıştır.
Programlar hakkındaki bu düşünce şeklini kendisinde en iyi barındıran programlama dili, C programlama dilidir. Bir C programı, işletim sistemi tarafından kontrol edilen standart girdideki veriyi alır, depolar ve sık sık manuel olarak yönetilmesi gereken fiziksel bellekteki gerekli değerleri elde eder, işaretçilerin(pointers) belirli bir bellek bloğuna işlenmesini gerektirir ve son olarak işletim sistemi tarafından kontrol edilen standart çıktı üzerinden tüm cıktıyı dönderir. C programları yazarken, programcılar önündeki bilgisayarın fiziksel mimarisi kadar eldeki sorun hakkında da çok şey anlamalıdırlar.
Fakat Von Neumann mimarisiyle bütünleşen bir bilgisayar modeli, hesaplamalar yapmanın tek yolu değildir. İnsanlar, bellek ayırma ve talimat setlerini düşünmekle ilgisi olmayan çok çeşitli hesaplamalar da yaparlar: Bir raftaki kitapları sıralama, matematikte bir fonksiyonun türevini alma, arkadaşlara talimatlar verme ve benzerleri gibi. C kodu yazarken, bilgisayarın belirli bir uygulamasını programlıyoruz. Fortran'ı kuran takıma liderlik eden John Backus, Turing Ödülü adlı dersinde “programlama Neumann modelinden koparılabilir mi?” diye sormuştu.
İşte bu soru da, bu kitaptaki ilk ünitenin konusu olan programlamayı anlamanın ikinci şekline yani Fonksiyonel Programlama'ya öncülük ediyor. Fonksiyonel programlama, Neumann tarzındaki programlamayı özgürleştirmeye çalışır. Fonksiyonel programlamanın temelleri, belirli bir uygulamayı aşan soyut, matematiksel hesaplama kavramlarıdır. Bu, genellikle onları açıklayarak problemleri basitçe çözen bir programlama metoduna öncülük eder. Fonksiyonel programlama, bilgisayarlara değil hesaplamalara odaklanarak programcının birçok zorlu sorunu çözmeyi çok daha kolay hale getirebilen güçlü soyutlamalara erişimini sağlar.
Bunun bedeli ise o işe başlama işinin çok daha zor olabilmesidir. Fonksiyonel programlamada düşünceler genellikle soyuttur ve ilk prensiplerdeki programlama fikrini oluşturarak başlamak zorundayız. Yararlı programlar oluşturabilmeden önce, birçok kavramın öğrenilmesi gerekir. Kitapta bu ilk ünite boyunca çalışirken, bir bilgisayar programlamanın da ötesindeki bir şekilde programlamayı öğreniyor olduğunuzu unutmayın.
C programlama dili, Von Neumann tarzı programlamanın neredeyse harika bir şekliyken; Haskell ise öğrenebileceğiniz en saf fonksiyonel programlama dilidir. Bir dil olarak Haskell Backus'un hayalini tam olarak karşılar ve daha bilindik programlama tarzlarına geri dönmenize izin vermez. Bu durum; Haskell'in öğrenimini diğer birçok programlama dilinden daha zor yapar fakat Haskell'i öğrenmede ilerledikçe, Fonksiyonel Programlama konusunda derin bir fikir edinmemeniz de imkansız hale gelir. Bu ünitenin sonunda; Haskell'i öğrenme yolculuğunuza hazırlanmanın yanı sıra tüm diğer fonksiyonel programlama dillerinin temellerini de anlamak için, fonksiyonel programlama konusunda yeterince güçlü bir temele sahip olacaksınız.
DERS 2: FONKSİYONLAR VE FONKSİYONEL PROGRAMLAMA
Ders 2'yi okuduktan sonra;
• Fonksiyonel programlamanın genel düşüncesini anlayabilecek,
• Haskell'de basit fonksiyonlar tanımlayabilecek,
• Haskell'de değişkenler bildirebilecek ve,
• Fonksiyonel programlamanın yararlarını açıklayabileceksiniz.
Haskell'i öğrenirken anlamanız gereken ilk başlık, fonksiyonel programlamanın ne olduğudur. Fonksiyonel programlama, ustalaşmak için zorlayıcı bir başlığı olan bir üne sahiptir. Bu şüphesiz doğru olmasına rağmen, fonksiyonel programlamanın temelleri ilginç bir şekilde açık ve anlaşılırdır. Öğrenmeniz gereken ilk şey, fonksiyonel bir programlama dilinde bir fonksiyonun olması ya da bulunmasıdır. Büyük ihtimalle, bir fonksiyon kullanmanın ne anlama geldiğine dair zaten iyi bir fikriniz vardır. Bu derste, fonksiyonların Haskell'de sadece kodunuzu daha kolay hale getirmekle kalmayıp aynı zamanda programlama hakkında tamamen yeni düşünme yollarına öncülük edeceği temel ve basit kuralları göreceksiniz.
Şuna Bir Gözatın: Siz ve arkadaşlarınız pizza alıyorsunuz. Menüde üç faklı ücreti ile üç farklı boyutta pizza var:
• 18 inch 20 $
• 16 inch 15 $
• 12 inch 10 $
Hangi seçimin paranız için size en iyi pizzayı vereceğini bilmek istiyorsunuz. Pizzanin kare başına dolarlik maliyetini verecek bir fonksiyon yazmak istiyorsunuz.
FONKSİYONLAR
Bir fonksiyon tam olarak nedir? Bu soru, fonksiyonel programlamayı keşfedip keşfetmeyeceğinizi sormak ve anlamak için önemli bir sorudur. Haskell'de fonksiyonların davranışı direkt olarak matematikten gelir. Matematikte sıklıkla, bir “x” parametresi alan ve bir “y” değeriyle eşleşen bazı “f” fonksiyonlarının olduğu anlamına gelen “ F(x) = y” gibi şeyler söyleriz. Matematikte her “x” paremetresi tek bir şeyle ve sadece tek bir tane “y” değeriyle eşleşebilir. Eğer verilen bir “f” fonksiyonu için eşitlik “ F(2) = 2, 000, 000 “ şeklindeyse, o fonksiyon asla “ F(2) = 2, 000, 001 “ durumunda olamaz.
Düşünceli bir okur şunu sorabilir: Karekök alma fonksiyonu nedir? 4'ün iki kökü var; 2 ve -2 yani açıkçası iki adet “y” ye işaret ettiğinde karekökü alınan “x” nasıl gerçek bir fonksiyon olabilir? Farkına varılacak ana şey ise şudur: “x” ve “y” aynı şey olmak zorunda değildir. Sqrt X'in yani X'in karekökünün pozitif bir kök olduğunu söyleyebiliriz, yani hem “x” hem de “y” pozitif reel sayılardır ki işte bu da bu sorunu çözmektedir. Fakat, pozitif bir reel sayıdan çift reel sayılara kadar bir fonksiyon olan kök X'imiz de olabilirdi. Bu durumda, her bir “x” kesinlikle tek bir çiftle eşleşir.
Haskell'de fonksiyonlar kesinlikle matematikteki gibi çalışırlar. Şekil 2.1 “simple” adlı bir fonksiyon gösterir:
Simple X = X
• Simple = fonksiyonun is midir? Haskell'de tüm fonksiyonlar küçük bir harfle başlar.
• Fonksiyon adından hemen sonra gelen “x” = fonksiyonun argümanı olup tüm fonksiyonlar en az bir argüman alır.
• En sondaki “x” = Fonksiyonun davranışı burada tanımlanır. Bu durumda sadece argümanınızı yani ilk X değerini dönderiyorsunuz.
“Simple” fonksiyonu tek bir “x” argümanı alır ve daha sonra dokunulmamış olan bu argümanı dönderir. Diğer birçok programlama dillerinden farklı olarak Haskell'de bir değer dönderiyor olduğunuzu belirtmenize gerek olmadığına dikkat edin. Haskell'de fonksiyonlar zaten bir değer Döndürmek zorundadırlar, yani bunu asla açık bir şekilde yapmak ya da belirtmek gerekmez. Şimdi “simple” adlı fonksiyonunuzu GHCi'ye yükleyebilir ve nasıl davrandığını görebilirsiniz. Bir fonksiyon yüklemek için, yapmanız gereken tüm şey; bir dosya içinde ona sahip olmak ve GHCi'de “:load <dosya_ismi> “ komutunu kullanmaktır:
GHCi> simple^2
2
GHCi> simple "dog"
"Dog"
NOT: Bu bölümde, komutları çalıştırmak ve sonuçları elde etmek için Haskell'in etkileşimli Read-Eval-Print Loop(REPL) yani oku-değerlendir-yazdır döngüsünü kullanıyoruz.
Haskell'deki tüm fonksiyonlar, onları matematikteki fonksiyonlar gibi davranmaya zorlayan üç kural izlerler:
1 - Tüm fonksiyonlar bir argüman almak zorundadırlar,
2 - Tüm fonksiyonlar bir değer Döndürmek zorundadırlar,
3 - Bir fonksiyon aynı argümanla çağrıldığında, yine aynı değeri döndermelidir.
Üçüncü kural, bir fonksiyonun temel matematiksel tanımının bir parçasıdır. Aynı argümanın her zaman aynı sonucu üretmesi gerektiği kuralı, bir programlama dilindeki fonksiyona uygulandığında; “referans şeffaflığı” olarak adlandırılır.
FONKSİYONEL PROGRAMLAMA
Eğer fonksiyonel programlama sadece bir çift X(yani X'in çoğulu olan)'lerden bir çift y(yine Y'nin çoğulu olan)'lere olan eşleştirmelerden ibaretse; o zaman, bunların programlamayla ne ilgisi var ki? 1930'larda, Alonzo Church adlı bir matematikçi sadece fonksiyonlar ve değişkenlerin(yani X'ler ve y'ler) kullanıldığı bir sistem mantığı oluşturma girişiminde bulundu. Bu sistem mantığı “lambda calculus” olarak adlandırılır. Lambda calculus modelinde; her şeyi fonksiyonlar olarak belirtirsiniz, true ve false fonksiyonlardır ve hatta tamsayılar bile fonksiyonlar olarak belirtilebilir.
Church'un hedefi: Dizi teorisinin matematiksel alanındaki birkaç sorununu çözmekti. Maalesef “lambda calculus” modeli bu sorunları çözemedi fakat Church'un çalışmasından çok daha ilginç bir şey göründü. Lamda Calculus modelinin, bir Turing makinesine eş değer olan evrensel bir hesaplama modeline izin verdiği ortaya çıktı.
NOT: Turing Makinesi Nedir? ===> Turing makinesi, ünlü bilgisayar bilimcisi Alan Turing tarafından geliştirilen soyut bir bilgisayar modelidir. Teorik bir bakış açısıyla; Turing modeli yararlıdır, çünkü sadece dijital bir bilgisayarda değil fakat herhangi bir bilgisayarda da neyin hesaplanabileceğini ve neyin hesaplanamayacağını düşünmenizi sağlar. Bu model, eğer her biri bir Turing makinesini simule edebilirse, bilgisayar bilimcilerinin hesaplama sistemleri arasındaki eşitliği görmesini de sağlar. Bunu, örneğin hem Java'da hem de Assembly dilinde hesaplayabileceğiniz hiçbir şey olmadığını göstermek için kullanabilirsiniz.
Lambda Calculus ve hesaplama arasındaki bu ilişki keşfi, “Church-Turing tezi(daha fazla bilgi için buraya bakın www.alanturing.net/turing_archive/ pages/reference%20articles/The%20Turing-Church%20Thesis.html)” olarak adlandırılır. Bu keşfe dair harika olan şey şudur ki; artık programlamada matematiksel olarak sağlam bir modeliniz var!
Kullandığınız birçok programlama dili mühendisliğin harika birer parçalarıdır fakat programların nasıl davranacağı hakkında az güvence sağlar. Matematiksel bir temel sayesinde Haskell, yazdığınız koddaki tüm bug ve hata türlerini kaldırabilir. Programlama dillerindeki en son araştırmalar, programların tam olarak beklediğiniz şeyleri matematiksel yollarla sağladığını gösteriyor. Ayrıca, çoğu programlama dili tasarımının matematiksel olmayan doğası, kullanabileceğiniz soyutlamaların dildeki mühendislik kararlarıyla sınırlandıgı anlamına gelir. Eğer matematiksel programlama yapabiliyorsanız, hem kodunuzla ilgili şeyleri geliştirebilir hem de matematiğin izin verdiği neredeyse sınırsız soyutlamalara erişim sağlayabilirdiniz. İşte bu, fonksiyonel programlamanın hedefidir: Kullanılabilir yoldan matematiğin gücünü programcıya sunmak.
PRATİKTE FONKSİYONEL PROGRAMLAMANIN DEĞERİ
Programlamada matematiksel modelin çeşitli pratik anlamları vardır. Tüm fonksiyonların değerler almak ve Döndürmek zorunda olması ve her zaman aynı argüman için aynı değeri Döndürmek zorunda olması gibi basit ya da temel kurallardan dolayı, Haskell güvenli bir programlama dilidir. Her zaman tam olarak beklediğiniz şekilde davrandıkları zaman programlar güvenlidir ve davranışları hakkında kolayca akıl yürütebilirsiniz. Güvenli bir programlama dili, programlarınızı beklendiği gibi davranmaya zorlayan bir dildir.
Hadi şimdi de güvenli olmayan ve fonksiyonlardaki basit kurallarımızı ihlal eden koda bir göz atalım. Varsayın ki, direkt yeni bir kod tabanı okuyorsunuz ve aşağıdakilere benzeyen kod satırlarıyla(fonksiyon çağrılarındaki saklı durum) karşılaşıyorsunuz:
tick()
İf(timeToReset){
reset()
}
Yukarıdaki kod açıkçası bir Haskell kodu değildir çünkü hem “tick” hem de “reset”, oluşturduğumuz kuralları ihlal eder. Fonksiyon ne herhangi bir argüman alır ne de herhangi bir değer dönderir. O zaman sorulacak soru şudur; bu fonksiyonlar ne yapıyorlar ve Haskell'de fonksiyonlardaki bu fark nasıl oluşuyor? Tahmin etmesi uzun sürmeyecektir ki;“tick” bir sayacı artırıyor ve “reset” o sayacı başlangıç değerine yeniden depoluyor. Hatta tam olarak haklı olmasak bile, bu akıl yürütme biçimi bize sorumuza ilişkin bilgi veriyor. Eğer bir fonksiyona bir argüman geçmiyorsanız ortamınızdaki bir değere erişiyor olmalısınız ve eğer bir değer döndermiyorsanız ortamınızdaki bir değeri değiştiriyor da olabilirsiniz. Programlama ortamınızdaki bir değeri değiştirdiğinizde, programın durumunu değiştiriyorsunuz. Durumu değiştirme dediğimiz olay ise, kodunuzda yan etki oluşturur ve bu yan etkiler o kod üzerinde akıl yürütmeyi zorlaştırabilir ve bu nedenle de güvensizdirler.
Büyük ihtimalle hem “tick” hem de “reset”, herhangi bir programlama dilinde kötü tasarlandığı düşünülen global bir değişkene(yani programdaki herhangi bir yerden ulaşılabilir olan bir değişkene) erişiyorlar. Fakat yan etkiler en basit, iyi yazılmış bir kod hakkında bile akıl yürütmeyi zorlaştırır. Bunu görmek için, “myList” adlı bir değerler dizisine bakacağız ve yerleşik işlevselliği kullanarak onu ters çevireceğiz. Aşağıdaki kod(standart kitaplıklardaki kafa karıştırıcı davranış) Python, Ruby ve JavaScript'da geçerlidir; eğer ne yaptığını anlayabilirseniz bakın:
MyList = [1,2,3]
myList.reverse()
NewList = myList.reverse()
Şimdi “newList” adlı değişkenin değerinin ne olmasını bekliyorsunuz? Bu, Python, Ruby ve JavaScript'ta geçerli bir kod olduğu için; newList adlı değişkenin değerinin aynı olması gerektiğini zannetmek mantıklı görünüyor. Bu üç dil için yukarıdaki kodun çıktıları ise aşağıdaki gibidir:
Ruby -> [3,2,1]
Python -> None (hiçbir şey)
JavaScript -> [1,2,3]
Üç dilde tıpı tıpına aynı kod için tamamen farklı üç cevap veya çıktı! Python ve Javascrip'ın her ikisi için de; “reverse) adlı fonksiyon çağırıldığında meydana gelen yan etkiler mevcuttur. “reverse” fonksiyonunu çağırmanın yan etkisi her dilde farklı ve programcıya görünür olmadığı için, her iki dil de farklı çıktılar üretiyor. Burada Ruby kodu, yan etkiler olmadan Haskell gibi davranıyor. İşte burada “referans şeffaflığı” adlı kavramın değerini görüyorsunuz. Haskell ile, herbir fonksiyonun hangi yan etkilere sahip olduğunu her zaman görebilirsiniz. İlk başta “tick” ve “reset” i çağırdığınızda, yapılan değişiklikler size görünmedi. Kaynak koda bakmaksızın, hangi değerleri hatta kaç tanesini kullanıyor ve değiştiriyor olduğunuzu kesinlikle bilme imkanınız yoktur. Haskell, fonksiyonların yan etkilere sahip olmasına izin vermez ki bu da tüm Haskell fonksiyonlarının neden bir argüman alıp bir değer döndermesi gerektiğini açıklar. Eğer Haskell fonksiyonları her zaman tek değer döndermeseydi, bu sefer gizli olana erişmeleri gerekirdi, bu da artık şeffaf olmadıkları anlamına gelirdi.
Haskell fonksiyonlarının bu küçük özelliği, önemli ölçüde tahmin etmesi daha kolay olan koda öncülük eder. Ruby'de bile programcının yan etkileri kullanmasına izin verilir. Diğer programcının kodunu kullanırken, bir fonksiyon veya metot çağırdığınızda ne olacağı hakkında hala hiçbir varsayımda bulunamazsınız. İşte Haskell buna izin vermediği için, herhangi bir programcı tarafından yazılan herhangi bir coda bakabilir ve davranışı hakkında akıl yürütebilirsiniz.
NOT: Birçok programlama dili, bir değeri artırmak için “++” işlecini kullanır. Örnegin: X++, X'in değerini artırır. Sizce Haskell'in bu şekilde çalışan bir operatörü(işleci) veya fonksiyonu var mıdır? Fonksiyonlardaki matematiksel kurallarımızı çiğneyeceği için, C gibi dillerde kullanılan “++” operatörü Haskell'de bulunamaz. En açık kural şudur ki; her zaman bir değişkende “++” operatörünü çağırırsınız fakat sonuç farklıdır.
1-Değişkenler
Haskell'de değişkenler açık ve anlaşılırdır. Aşağıdaki kodda (ilk değişkeninizi tanımlama), 2'yi X adlı değişkene atıyorsunuz:
X = 2
Haskell'deki değişkenlerle ilgili tek tuzak, onların gerçekte asla bir değişken olmamalarıdır. Eğer aşağıdaki Haskell kod parçasını derlemeye çalışmış olsaydınız, sonraki listede gösterildiği gibi bir hata alırdınız. Değişkenler, değişken değildir yani bir kere tanımlandı mı artık daha değişemezler. Örneğin aşağıdaki kodlarda, ikinci satır X'in ilk değerini değiştirdiği için derlemeyecektir:
X = 2
X = 3 (ilk değer 3'e değiştiği için derlemeyecektir)
Haskell'de değişkenler üzerine düşünmenin en iyi yolu onları “tanımlamalar” olarak düşünmektir. Bir kez daha görüyoruz ki; matematiksel düşünme, tipik olarak kodla ilgili düşünme şeklinizin yerini alıyor. Sorun şudur ki; birçok programlama dilinde yeniden değişken atama, birçok sorunu çözmek için gereklidir. Değişkenleri değiştirememe imkansızlığı, ayrıca “referans şeffaflığı” kavramıyla da ilgilidir. Bu uyulması gereken katı bir kural gibi görünebilir fakat faydası şudur ki; her zaman bir fonksiyon çağırdıktan sonra tıpatıp aynı kalacağını bilirsiniz.
NOT: Bir “++” operatörü olmayan diller bile sıklıkla bir değeri artırmak için de kullanılan “+=” operatörüne izin verirler. Örneğin “ X += 2 “, X değişkenini 2 ile artırır. “ += “ i kurallarımıza uyan bir fonksiyon olarak düşünebilirsiniz: Bir değer alır ve bir değer dönderir. Peki bu, “ += “ operatörünün Haskell'de var olabileceği anlamına mı gelir? “+=” operatörü, tıpkı “++” operatöründe olduğu gibi, bir argüman alıp döndermesine rağmen; Haskell'de de her zaman “ += “ operatörünü çağıtabilirsiniz, farklı bir sonuç elde edersiniz.
Programlamada değişkenlerin ana faydası, kodunuzu açıklığa kavuşturmak ve tekrardan kaçınmaktır. Örneğin “ calcChange “ adlı bir fonksiyon istediğinizi düşünün. Bu fonksiyon iki argüman alır: Borçlu olunan(owed) ve ödenen(given) ne kadar. Eğer size yeterli para veriliyorsa, farkı bulun ya da dönderin. Fakat size yeterli para verilmiyorsa, zarara sokan dolarlar vermek istemezsiniz: Yani çıktı olarak 0(sıfır) döndereceksiniz. İşte bunu yazmanın bir yolu:
CalcChange owed given = if given - owed > 0
Then given - owed
Else 0
Bu fonksiyonda iki şey hatalıdır:
• Küçücük bir fonksiyonun bile okuması zordur. Her seferinde ne olduğuna dair akıl yürütmek zorunda kaldığınız “ given - owed “ ifadesini görüyorsunuz. Her ne pahasına olursa olsun, çıkarma işleminden daha karmaşık bir şey için bu hoş olmayacaktır.
• Hesaplamanızı tekrarlıyorsunuz! Çıkartma işlemi basit bir işlemdir fakat eğer bu işlem daha maliyetli bir işlem olmuş olsaydı, gereksiz bir şekilde kaynak tüketiyor olurdunuz.
Haskell bu sorunları özel bir “ where “ yantümcesini kullanarak çözer. İşte bir önceki fonksiyonun “ where “ ifadesiyle yazılmış şekli:
CalcChange owed given = if change > 0
Then change
Else 0
Where change = given owed (burada “given - owed” farkı bir kere hesaplanıyor ve Change'e atanıyor)
İlginç olarak dikkatinizi çekmesi gereken ilk şey; “where” ifadesinin değişkenleri yazmak için kullanılan normal sırayı tersine çeviriyor olmasıdır. Çoğu programlama dilinde, değişkenler kullanılmadan önce bildirilirler. Çoğu programlama dilindeki bu gelenek kısmen, durumu değiştirebilmenin yan ürünü yani farklı bir şeklidir. Değişken sırası önem taşır, çünkü her zaman bir şeyin değerini atadıktan sonra yeniden atayabilirsiniz. Haskell'de referans şeffaflığından dolayı, bu bir sorun teşkil etmiyor. Haskell yaklaşımı ile birlikte bir okunabilirlik kazancı da var: Eğer algoritmayı okursanız, amaç gayet açıktır.
NOT: Aşağıdaki where ifadesinin eksik kısmını tamamlayınız:
DoublePlusTwo X = doubleX + 2
Where doubleX = __________
Yukarıdaki sorunun cevabı aşağıdaki gibi olmalıydı:
DoublePlusTwo X = doubleX + 2
Where doubleX = x*2
2 - Değişken Olan Değişkenler
Değişiklik hayatın kaçınılmaz bir parçası olduğu için, bazen yeniden atanabilir değişkenlere sahip olmak mantıklı hale gelir. Bu hallerden biri, Haskell REPL, GHCi'da çalışırken meydana gelir. GHCi'da çalışırken, değişkenleri yeniden atamanıza izin verilir. İşte bir örnek:
GHCi> X = 7
GHCi> X
7
GHCi> X = [1,2,3]
GHCi> X
[1,2,3]
GHC'nin 8.sürümünden önce, Haskell'de onları diğer değişkenlerden farklı olarak işaretlemek için, GHCi'de değişkenlerin “ let “ anahtar kelimesi ile başlatılması gerekiyordu. Eğer isterseniz GHCi'de hala değişkenleri “let” anahtar kelimesini kullanarak tanımlayabilirsiniz:
GHCi> let X = 7
GHCi> X
7
Tek satır fonksiyonların aynı şekilde tanımlanabileceğini de belirtmek gerekir:
GHCi> let F X = x^2
GHCi> F 8
64
Haskell'de diğer birkaç özel durumda, bu şekilde kullanılan “ let “ anahtar kelimesini göreceksiniz. Kafa karıştırıcı olabilir fakat bu farklılık öncelikli olarak gerçek dünyadaki işleri daha az sinirbozucu hale getirmek içindir. GHCi'de değişkenlerin tanımlamalarını değiştirebilmenin özel bir durum olduğunu kabullenmek önemlidir. Haskell katı olabilmesine rağmen, farklı bir değişkenle deneyimlemek istediğiniz her seferinde, GHCi'yi yeniden başlatmak zorunda kalmak sinir bozucu olurdu.
NOT: Aşağıdaki kodda X adlı değişkenin son değeri nedir?
GHCi> let X = simple simple
GHCi> let X = 6
Değerleri yeniden atayabileceğiniz için, X değişkeninin son değeri 6'dır.
ÖZET
Bu derste amacımız, sizi fonksiyonel programlama ile tanıştırmak ve Haskell'de basit fonksiyonlar yazmaktı. Fonksiyonel programlamanın bir fonksiyonun davranışı üzerinde sınırlar ya da kısıtlamalar koyduğunu gördünüz. Bu kısıtlamalar aşağıdaki gibidir:
• Bir fonksiyon her zaman bir argüman almak zorundadır
• Bir fonksiyon her zaman bir değer Döndürmek zorundadır
• Aynı argümanla aynı fonksiyonu çağırmak, her zaman aynı sonucu Döndürmek zorundadır.
Bu üç kuralın Haskell'de program yazma şekliniz üzerinde derin sonuçları vardır. Bu tarzda kod yazmanın başlıca yararı şudur ki; programlarınız ve tahmin edildiği gibi onların davranışları hakkında akıl yürütmesi çok daha kolaydır. Hadi bunu anlayıp anlamadığınızı görelim:
Soru - 1: Haskell'in “ if then else “ ifadesini “ calcChange “ adlı fonksiyonu yazmak için kullandınız. Haskell'de tüm “ if “ ifadeleri bir “ else “ bileşeni içermek zorundadır. Fonksiyonlar için verilen üç kuralımız göz önüne alındığında, neden tek başına bir “ if “ ifadeniz olamıyor?
Soru - 2: Sırasıyla “ n “ adlı bir argümanı artıran(increment), çarpan(double) ve karesini alan(square); “ inc”, “ double “ ve “square” adlı fonksiyonlar yazınız.
Soru - 3: “ n “ değeri alan bir fonksiyon yazın. Eğer “ n “ çift bir sayıysa fonksiyon “n-2” yi, tek sayıysa da 3X(n-1)'in değerini döndürsün. Sayının çift olup olmadığını kontrol etmek için, Haskell'in ya “ even “ ya da “ mod(Haskell'in modül fonksiyonu) “ fonksiyonunu kullanabilirsiniz.
Dosya Ekleri
Son düzenleyen: Moderatör: