Haskell Notları
Merhaba bu postta haskelle alakalı kendi öğrenme sürecimle eş zamanlı olarak paylaşımlarda bulunacağım. O yüzden şimdiden “şunlar şunlar olacak” diyemiyorum. Uçtan uca ayrıntılı bir tutorial değil daha çok önemli noktaların vurgulandığı bir paylaşım olacak.
Links first ;
https://www.emrah.com/notlar/haskell_notlari.txt
Haskell Nedir ?
Fonksiyonel bir programlama dilidir.
Haskell Syntax
@ işareti
Sentetik şekerdir. ps@(p:pt) ifadesi bize şunları söyler;
- Liste : ps
- Listenin başı : p
- Listenin kuyruğu : pt
. (dot) Operatörü
2 fonksiyonu birleştirir. Matematikteki
- f (g x) = (f.g) x
ifadesindeki gibi, fonksiyonları birleştirir.
- a.b
Dediğimiz zaman, b fonksiyonu parametreleri işler dönen değerleri de a fonksiyonu işler.
Fonksiyon yazma
DOĞRU :
kaos :: Integer -> Integer
kaos x = x+3
main = do
let parcala x = x/10
print(kaos 3)
YANLIŞ :
main = do
kaos :: Integer -> Integer
kaos x = x+3
print(kaos 3)
Evet şimdi yukarıdan neler öğrenmemiz gerekiyor özet geçelim;
- Parametre ve output tiplerini Type1 -> Type2 şeklinde belirterek fonksiyonumuzu yazmak istiyorsak bunu main dışında yapmak gerekiyor.
- main içerisinde fonksiyon yazacaksak parametre ve return tiplerini belirtmeden başına let koyarak yapıyoruz.
Guards (|)
Fonksiyonun çeşitli durumlara göre farklı sonuçlar döndürmesi için kullanılır. Hemen örnek verelim;
maxThree :: Integer -> Integer -> Integer -> Integer
maxThree x y z
| x>=y && x>=z = x
| y>=z = y
| otherwise = z
Görüldüğü gibi yukarıda input olarak verilen 3 elemandan max olanı dönmüştür.
Haskell Veriyapıları
List
- Tüm elemanlar aynı tiptedir.
List Operations, patterns etc.
List Patterns
Adı üstünde, liste patternleri. Listelerin çeşitli durumlarını check ve gerektiğinde match etmek için kullanılırlar.
- empty list: []
- nonempty list: x:xs
- list with exactly one element: [x]
- list with exactly two elements: [x1,x2]
- list with at least two elements: x1:x2:xs
Burada x:xs‘e bir örnek vererek ayrı bir parantez açmak istiyorum.
concat :: [[a]] -> [a]
concat [] = []
concat (xs:xss) = xs ++ concat xss
Yukarıda parametre olarak nested list alan ve aldığı bu listelerin elemanlarını tek bir listede birleştiren concat fonksiyonu verilmiştir. 3.satıra bakıldığında xs:xss‘yi görüyoruz. Bu ifade sadece xss listesinin boş olup olmadığını kontrol etmekle kalmaz, boş değilse ilk elemanını xs’ye atar.
tail
Listeyi alır, ilk eleman hariç kalanını liste olarak döndürür.
Prelude> :t tail
tail :: [a] -> [a]
head
Liste alır, ilk elemanı döndürür.
Prelude> :t head
head :: [a] -> a
List indexing (!!)
!! ile yapılır.
-- [1, 2, 3] !! 0 ~> 1
-- [1, 2, 3] !! 2 ~> 3
-- [1, 2, 3] !! 3 ~> error
Append (++)
++ ile yapılır
--[1,2,3]++[4,5] ~>[1,2,3,4,5]
reverse
Listeyi ters çevirir.
Prelude> reverse "abcd"
"dcba"
Prelude> reverse [1,3,5]
[5,3,1]
Custom Types
data Area = Rectangle Float Float
surface :: Area -> Float
surface (Rectangle a b) = a*b
main = do
print (surface (Rectangle 10 13))
print (surface $ Rectangle 10 13)
Area , yeni veritipimizin adıdır. Rectangular , value constructordur. Yani Rectangular kendisinden sonra verilen iki Float’ın birlikte Area tipinde bir veri oluşturduğunu belirtir.
Value constructor OOP’deki class constructor gibi düşünülebilir. Hatırlarsanız orada da bu constructorları fonksiyon şeklinde kullanır, parametrelerini verirdik. Onlar da bize obje return ederdi. Güzel zamanlardı. Burada da aynı şekilde bir kullanım söz konusu. Rectangle constructorunun kullanımında görüldüğü gibi fonksiyonlar hem parantez içerisine alınarak hem de başına $ işareti konularak çalıştırılabilir.
Patter Matching Ve Guards
Pattern Matching
Links ;
https://www.tutorialspoint.com/haskell/haskell_functions.htm
https://stackoverflow.com/questions/2225774/haskell-pattern-matching-what-is-it
Bir fonksiyon veya custum data tipini birden fazla koşula bağlı yazabileceğimizi biliyoruz. Örneğin ;
fib 0 = 1
fib 1 = 1
fib n | n >= 2
= fib (n-1) + fib (n-2)
Patternler matematikteki parçalı fonksiyonlar gibidir. Örneğin yukarıda verilen fib fonksiyonu 0, 1 ve 2’ye eşit ve büyük n değerleri için için farklı davranışlar sergiliyor ve her durum parçalı fonksiyon gibi ayrıyeten belirtilmiş. Bizim verdiğimiz parametreye göre bu fonksiyonun pattern‘lerinden birisi tetiklenecek. Bu olaya pattern matching diyoruz.
Guards
fib n | n == 0 = 1
| n == 1 = 1
| n >= 2 = fib (n-1) + fib (n-2)
Guardslar “ | ” işaretiyle ayrılan ifadelerdir. Burada ise fonksiyonun farklı durumlara göre farklı davranışlar gösterecek kısımları guardslarla yazılmış. Bu görüldüğü üzere pattern matching’ten biraz farklı. Efsaneler der ki aslında önerilen kullanım şekli pattern matchingmiş fakat yazılımcılar guardslarla yazmanın daha okunaklı olduğunu düşünür ve böyle yazarlarmış. |
where
roots :: (Float,Float,Float) -> (Float,Float)
roots (a,b,c) = (x1,x2) where
delta = sqrt (b*b-4*a*c)
x1 = (-b + delta) / 2*a
x2 = (-b - delta) / 2*a
main = do
putStrLn "The roots of the polynomial equation are : "
print(roots(1,-8,6))
örnekte görüldüğü gibi where ile fonksiyonun dönüş değerleri ve o dönüş değerlerini bulurkan kullandığımız ara değerleri sırasıyla belirtiriz.
case
case expression of pattern -> result
pattern -> result
pattern -> result
...
Haskell Functions
map
Birinci parametre olarak verilen fonksiyonu ikinci parametre olarak verilen listedeki bütün elemanlara tek tek uygular, geriye işlenmiş listeyi döner.
map (3*) [1,2,3,4]
OUTPUT:
[3,6,9,12]
zipWith
zipWith replicate [3, 4] ['a', 'b']
zipWith (+) [1,2,3] [3,2,1]
OUTPUTS :
[“aaa”,”bbbb”] [4,4,4]
Haskell Terms
Pure Functions
Bir fonksiyonun sonucu sadece parametreleri tarafından belirleniyorsa bu fonksiyona pure function denir. Matematikteki fonksiyonlar buna örnektir. Yazılımda bilindiği gibi bazen fonksiyon içerisinde global değişkenler veya kullanıcan alınan girdi değişkenleri de bulunmakta ve fonksiyonun dönüş değerine etkileri olabilmektedir. Pure fonksiyonlarda ise fonksiyonun içerisinde bu şekilde global değişkenler veya kullanıcı girdileri bulunmaz.
Tail Recursion
Eğer bir fonksiyon en sonunda kendisini çağırıp bitiyorsa bu fonksiyon tail recursive fonksiyondur. Bu sayede fonksiyonun hesaplaması icab eden başka bir şey kalmadığından stack’e konulması gerekmez, orada bitirilir. Böylece memory şişmez. Burada dikkat edilmesi gereken bir şey var;
sum [] = 0
sum (x:xs) = x + sum xs
Örneğin şu yukarıdaki fonksiyon tail recursive DEĞİLDİR!.
Peki neden ?
Çünkü bu fonksiyon sonda her ne kadar kendisini çağırsa da henüz bitmemiştir. sum xs hesaplandıktan sonra bu hesaplanan değeri x ile toplama işlemini yapması gerekmektedir. Dolayısıyla bu fonksiyon tail recursive değildir, stack’e konulur. Fakat aşağıdaki fonksiyon ise bir tail recursive fonksiyondur.
sum [] acc = acc
sum (x:xs) acc = sum xs (x + acc)
Bu fonksiyon sum xs (x + acc) fonksiyonunu çağırmış ve bitmiştir, dolayısıyla çağırdığı bu ifadenin değeri haricinde hesaplanacak bir şey kalmamıştır. Durum böyle olunca fonksiyonun stack’e konulması gerekmez.
Sample Codes
Get List Except Last Element
getexceptlast :: [xs] -> [xs]
getexceptlast xs
| length xs == 1 = []
| length xs /= 1 = [head xs] ++ getexceptlast (tail xs)
main = do
print(getexceptlast [1,2,3,4])
ITU Functional Programming Course - Quiz 1
The Syracuse sequence is generated by starting with a natural number and repeatedly applying the following function until reaching 1:
syr(x) = if even x then x/2 elif odd x then 3*x+1
Answer the following questions based on this problem. When writing functions, always include the type signature.
Write a primitive recursive syracuse function that given a number, returns the Syracuse sequence starting from that number. For example, syracuse 5 should return [5, 16, 8, 4, 2, 1]. Explain why this function is not tail recursive.
syr :: Integer -> [Integer]
syr x
| even x = [x] ++ syr (x `div` 2)
| odd x && x/=1 = [x] ++ syr (3*x+1)
| otherwise = [1]
main = do
print(syr 5)
#
Hight Order Functions
First Order Functions
Parametre olarak sadece data alır ve sonuç olarak da geriye sadece data döner.
High-order Functions
Parametre olarak fonksiyon alır ve geriye de fonksiyon döner.
Leave a comment