Haskell Notları

6 minute read

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;

  1. Liste : ps
  2. Listenin başı : p
  3. 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]

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