Skip to content

Haskell

H Haskell είναι μια αμιγώς συναρτησιακή γλώσσα προγραμματισμού (pure functional programming language) οκνηρής αποτίμησης (lazy evaluation). Η Haskell διαθέτει ισχυρό στατικό σύστημα τύπων (strong static type system) που αντιλαμβάνεται τους τύπους που απαιτούνται για τις συναρτήσεις και τις παραμέτρους τους.

Τα παραδείγματα κώδικα που ακολουθούν είναι από το "7 Languages in 7 Weeks, by Bruce A. Tate - A Pragmatic Guide to Learning Programming Languages" και από άλλες πηγές.

Εγκατάσταση της Haskell

H Haskell (η Glasgow Haskell) μπορεί να εγκατασταθεί χρησιμοποιώντας το GHCup, ακολουθώντας τις οδηγίες από το https://www.haskell.org/downloads/.

Εναλλακτικά, για την εγκατάσταση της Haskell μπορεί να χρησιμοποιηθεί το εργαλείο stack, ακολουθώντας τις οδηγίες από το https://docs.haskellstack.org/en/stable/.

Ο διερμηνευτής της Haskell

Ενεργοποίηση και τερματισμός του διερμηνευτή ghci της Haskell.

$ ghci
GHCi, version 9.4.8: https://www.haskell.org/ghc/  :? for help
ghci> "Hello World"
"Hello World"
ghci> :quit
Leaving GHCi.
$
Αν έχει εγκατασταθεί η Haskell με το εργαλείο stack τότε η ενεργοποίηση του ghci γίνεται ως εξής:

$ stack ghci
tests> initial-build-steps (lib)
Configuring GHCi with the following packages: tests.
GHCi, version 9.2.8: https://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /Users/chgogos/.cache/stack/ghci-script/2a3bbd58/ghci-script
ghci> 2 ^100
1267650600228229401496703205376
ghci> :q
Leaving GHCi.
$

Εκφράσεις και βασικοί τύποι

Αριθμοί

ghci> 1 + 2
3
ghci> (+) 1 2
3
ghci> 1 + 2 * 3
7
ghci> 1 + 2 * 3.0
7.0
ghci> 2 ^ 10
1024
ghci> div 10 3
3
ghci> 10 `div` 3 -- div ως infix τελεστής με backticks `
3
ghci> mod 10 3
1
ghci> 10 `mod` 3 -- mod ως infix τελεστής με backticks `
1
ghci> gcd 24 56
8 

Λεκτικά

ghci> "hi"
"hi"
ghci> "hello " ++ "world"
"hello world"
-- δεικτοδότηση λεκτικού με το !!
ghci> "hello" !! 1
'e'

Χαρακτήρες

Ένα λεκτικό είναι μια λίστα χαρακτήρων.

ghci> 'a'
'a'
ghci> ['a', 'b']
"ab"

Λογικές τιμές

ghci> 1 == 2-1
True
ghci> 1 /= 1
False
ghci> "Arta" == ['A','r','t','a']
True

Συναρτήσεις

Οι συναρτήσεις στην Haskell αποτελούνται από δύο τμήματα. Ένα προαιρετικό τμήμα δήλωσης του τύπου της συνάρτησης. Ένα τμήμα με την υλοποίηση της συνάρτησης. Η Haskell έχει ένα ισχυρό σύστημα συμπερασμού για τον εντοπισμό των τύπων που χρησιμοποιούνται στις συναρτήσεις.

Πρόσδεση (binding) της μεταβλητής x σε τοπική εμβέλεια.

ghci> let x = 42
ghci> x
42

Πρόσδεση της συνάρτησης double σε τοπική εμβέλεια.

ghci> let double x = 2 * x
ghci> double 21
42

Αποθήκευση συνάρτησης σε αρχείο.

Αρχείο day1.hs με ορισμούς των συναρτήσεων: double, fact, factpm, factg, fibpm, fibt, fibc, size, prod, allEven
day1.hs
-- συνάρτηση με όνομα double που επιστρέφει την παράμετρό της διπλασιασμένη
double :: Num a => a -> a
double x = x + x

-------------------------------------------------

-- factorial (3 τρόποι)
-- με conditional
factc :: Integer -> Integer
factc x = if x == 0 then 1 else factc (x -1) * x

-- με guards
factg :: Integer -> Integer
factg x
  | x > 1 = x * factg (x -1)
  | otherwise = 1

-- με pattern matching
factpm :: Integer -> Integer
factpm 0 = 1
factpm x = x * factpm (x - 1)

-------------------------------------------------

-- ακολουθία fibonacci (3 τρόποι)
-- με pattern matching
fibpm :: Integer -> Integer
fibpm 0 = 1
fibpm 1 = 1
fibpm x = fibpm (x - 1) + fibpm (x - 2)

-- με tuples
fibTuple :: (Integer, Integer, Integer) -> (Integer, Integer, Integer)
fibTuple (x, y, 0) = (x, y, 0)
fibTuple (x, y, index) = fibTuple (y, x + y, index - 1)

fibResult :: (Integer, Integer, Integer) -> Integer
fibResult (x, y, z) = x

fibt :: Integer -> Integer
fibt x = fibResult (fibTuple (0, 1, x))

-- με σύνθεση συναρτήσεων
fibNextPair :: (Integer, Integer) -> (Integer, Integer)
fibNextPair (x, y) = (y, x + y)

fibNthPair :: Integer -> (Integer, Integer)
fibNthPair 1 = (1, 1)
fibNthPair n = fibNextPair (fibNthPair (n - 1))

fibc :: Integer -> Integer
fibc = fst . fibNthPair

-------------------------------------------------

-- μήκος λίστας
size :: Num p => [a] -> p
size [] = 0
size (h : t) = 1 + size t

-------------------------------------------------

-- γινόμενο στοιχείων λίστας
prod :: Num p => [p] -> p
prod [] = 1
prod (h : t) = h * prod t

-------------------------------------------------

-- διατήρηση μόνο των άρτιων τιμών μιας λίστας
allEven :: [Integer] -> [Integer]
allEven [] = []
allEven (h : t) = if even h then h : allEven t else allEven t
-- ο κώδικας βρίσκεται στο αρχείο day1.hs
double :: Integer -> Integer
double x = 2 * x

Φόρτωση κώδικα, κλήση συνάρτησης.

$ ghci
GHCi, version 9.4.8: https://www.haskell.org/ghc/  :? for help
ghci> :load day1.hs
[1 of 2] Compiling Main             ( day1.hs, interpreted )
Ok, one module loaded.
ghci> double 10
20
ghci> :quit
Leaving GHCi

ή

$ ghci day1.hs
GHCi, version 9.4.8: https://www.haskell.org/ghc/  :? for help
[1 of 2] Compiling Main             ( day1.hs, interpreted )
Ok, one module loaded.
ghci> double 10
20
ghci> :quit
Leaving GHCi

Αναδρομή (recursion)

Ορισμός της συνάρτησης fact απευθείας στο ghci με τη χρήση της if-then-else.

ghci> let fact x = if x == 0 then 1 else fact (x - 1) * x
ghci> fact 3
6

Ταίριασμα προτύπων (pattern matching)

Ορισμός της multiline συνάρτησης factpm απευθείας στο ghci, προσοχή στη χρήση των συμβόλων :{ και :}.

ghci> :{
ghci| factpm :: Integer -> Integer
ghci| factpm 0 = 1
ghci| factpm x = x * factpm (x - 1)
ghci| :}
ghci> factpm 10
3628800

Φρουροί (guards)

Οι φρουροί είναι συνθήκες που περιορίζουν τις τιμές των ορισμάτων όπως στη συνέχεια. Όταν η συνθήκη ενός φρουρού ικανοποιείται η Haskell καλεί την κατάλληλη συνάρτηση.

-- ο κώδικας βρίσκεται στο αρχείο day1.hs
factg :: Integer -> Integer
factg x
    | x > 1 = x * factg (x-1)
    | otherwise 1
$ ghci day1.hs 
ghci> factg 5
120

Πλειάδες και λίστες

Στις πλειάδες τα στοιχεία μπορούν να είναι διαφορετικού τύπου ενώ στις λίστες όλα τα στοιχεία είναι του ίδιου τύπου.

Μια λίστα και μια πλειάδα. Με το :type μπορούμε να δούμε τον τύπο μιας έκφρασης.

ghci>:type [1,2,3]
[1,2,3] :: Num a => [a]
ghci> :type ('1', "2", 3, 4)
('1', "2", 3, 4) :: (Num c, Num d) => (Char, String, c, d)

Πρώτο (fst) και δεύτερο (snd) στοιχείο σε μια πλειάδα

ghci> fst (1,2)
1
ghci> snd (1,2)
2

Συνάρτηση εύρεσης όρων της ακολουθίας fibonacci.

-- ο κώδικας βρίσκεται στο αρχείο day1.hs
fib :: Integer -> Integer
fib 0 = 1
fib 1 = 1
fib x = fib (x - 1) + fib (x - 2)
ghci> fib 10
10946

Υλοποίηση ταχύτερης έκδοσης της συνάρτησης fib (με χρήση πλειάδων).

-- ο κώδικας βρίσκεται στο αρχείο day1.hs
fibTuple :: (Integer, Integer, Integer) -> (Integer, Integer, Integer)
fibTuple (x, y, 0) = (x, y, 0)
fibTuple (x, y, index) = fibTuple (y, x + y, index - 1)

fibResult :: (Integer, Integer, Integer) -> Integer
fibResult (x, y, z) = x

fib :: Integer -> Integer
fib x = fibResult (fibTuple (0, 1, x))
ghci> fib 100
354224848179261915075

head, tail, init, last σε λίστες

ghci> head [1,2,3,4]
1
ghci> tail [1,2,3,4]
[2,3,4]
ghci> let (h:t) = [1,2,3,4]
ghci> h
1
ghci> t
[2,3,4]
ghci> init [1,2,3,4]
[1,2,3]
ghci> last [1,2,3,4]
4

Σύνθεση (composition)

ghci> let second = head . tail
ghci> second [1,2,3]
second = 2

Διάσχιση λιστών

Μέγεθος λίστας

-- ο κώδικας βρίσκεται στο αρχείο day1.hs
size [] = 0
size (h:t) = 1 + size t
ghci> size [1,2,3,4]
4

Γινόμενο τιμών λίστας

ghci> product [1,2,3,4]
24

Συνδυασμός λιστών με το zip

ghci> zip [1,2,3,4] [5,6,7,8]
[(1,5),(2,6),(3,7),(4,8)]

Συνδυασμός λιστών με συνάρτηση με το zipWith

ghci> zipWith (*) [1,2,3,4] [5,6,7,8]
[5,12,21,32]

Δημιουργία λιστών

Το σύμβολο : λέγεται cons και διαχωρίζει την κεφαλή από την ουρά της λίστας.

ghci> 1:[2,3]
[1,2,3]

Συνάρτηση που επιστρέφει μια λίστα με τους άρτιους αριθμούς μιας λίστας

-- ο κώδικας βρίσκεται στο αρχείο day1.hs
allEven :: [Integer] -> [Integer]
allEven [] = []
allEven (h:t) = if even h then h:allEven t else allEven t
ghci> allEven [1,2,3,4,5,6]
[2,4,6]

Περιοχές τιμών (ranges)

ghci> [1..10]
[1,2,3,4,5,6,7,8,10]
ghci> [0,5..10]
[0,5,10]
ghci> [2,1.5..0]
[2.0,1.5,1.0,0.5,0.0]

Μη φραγμένες περιοχές τιμών

ghci> take 5 [1..]
[1,2,3,4,5]

Περιφραστικές λίστες (list comprehensions)

ghci> [x * 2 | x <- [1, 2, 3]]
[2,4,6]
ghci> [ (y, x) | (x, y) <- [(1, 2), (2, 3), (3, 1)]]
[(2,1),(3,2),(1,3)]
ghci> [ (y, x) | (x, y) <- [(1, 2), (2, 3), (3, 1)]]
[(2,1),(3,2),(1,3)]
ghci> let a = [1..10]
ghci> let b = [0,5,20]
ghci> [(x,y) | x <- a, y<-b]
[(1,0),(1,5),(1,20),(2,0),(2,5),(2,20),(3,0),(3,5),(3,20),(4,0),(4,5),(4,20),(5,0),(5,5),(5,20),(6,0),(6,5),(6,20),(7,0),(7,5),(7,20),(8,0),(8,5),(8,20),(9,0),(9,5),(9,20),(10,0),(10,5),(10,20)]
ghci> [(x,y) | x <- a, y<-b, x < y]
[(1,5),(1,20),(2,5),(2,20),(3,5),(3,20),(4,5),(4,20),(5,20),(6,20),(7,20),(8,20),(9,20),(10,20)]

Συναρτήσεις υψηλότερης τάξης

Αρχείο day2.hs με ορισμούς των συναρτήσεων: squareAll, myRange
day2.hs
-- εφαρμογή συνάρτησης σε όλα τα στοιχεία μιας λίστας
squareAll :: Num b => [b] -> [b]
squareAll list = map square list
  where
    square x = x * x

-------------------------------------------------

-- δημιουργία μιας άπειρης λίστας
myRange :: Num t => t -> t -> [t]
myRange start step = start : (myRange (start + step) step)

Ανώνυμες συναρτήσεις

Η σύνταξη των ανώνυμων συναρτήσεων (λάμδα συναρτήσεων) στη Haskell είναι η ακόλουθη:

(\param1 .. paramn -> function_body)

Μια ανώνυμη συνάρτηση επιστροφής του τετραγώνου της παραμέτρου της και η κλήση της με παράμετρο την τιμή 5.

ghci> (\ x -> x * x) 5
25

Μια ανώνυμη συνάρτηση με δύο παραμέτρους:

ghci> (\ x y-> x * y) 5 7
35

map

ghci> map (\x -> x * x) [1, 2, 3]
[1, 4, 9]
-- ο κώδικας βρίσκεται στο αρχείο day2.hs
squareAll :: Num b => [b] -> [b]
squareAll list = map square list
    where square x = x * x
ghci> squareAll [1, 2, 3]
[1,4,9]

H (+1) είναι μια μερικά εφαρμοσμένη συνάρτηση. Πρόκειται για τη συνάρτηση + στην οποία έχει προσδεθεί η μια από τις δύο παραμέτρους με τιμή 1. Η δεύτερη παράμετρος σε κάθε κλήση της συνάρτησης θα είναι μια τιμή της λίστας.

ghci> map (+1) [1,2,3]
[2,3,4]

filter

ghci> filter odd [1, 2, 3, 4, 5]
[1, 3, 5]

foldl

ghci> foldl (\x sum -> sum + x) 0 [1, 2, 3, 4]
10
ghci> foldl (+) 0 [1,2,3,4]
10
ghci> foldl1 (+) [1,2,3,4]
10

Μερικά εφαρμοσμένες συναρτήσεις και currying

Στη Haskell κάθε συνάρτηση ουσιαστικά έχει μια μόνο παράμετρο.

ghci> let prod x y = x * y
ghci> prod 3 4
12
ghci> (prod 3) 4
12
ghci> tripple = prod 3
ghci> tripple 4
12

Οκνηρή αποτίμηση

H Haskell επιτρέπει άπειρες λίστες να επιστρέφονται από συναρτήσεις.

Ορισμός μιας άπειρης λίστας.

-- ο κώδικας βρίσκεται στο αρχείο day2.hs
myRange :: Num t => t -> t -> [t]
myRange start step = start:(myRange (start + step) step)
ghci> take 5 (myRange 10 2)
[10,12,14,16,18]

Σύνταξη της Haskell για μη φραγμένες περιοχές τιμών.

ghci> take 10 [1..]
[1,2,3,4,5,6,7,8,9,10]
ghci> take 10 [1,3..]
[1,3,5,7,9,11,13,15,17,19]

Τύποι

Πρωτογενείς τύποι

ghci> :type 'a'
'a' :: Char
ghci> :type True
True :: Bool
Αρχείο day3.hs με ορισμούς τύπων δεδομένων: Suit, Rank, Card, Hand, Tree
day3.hs
-- τύπος ορισμένος από το χρήστη με δύο τιμές
data Boolean = True | False deriving (Show)

-------------------------------------------------

-- τύποι και συναρτήσεις για παιχνίδια με κάρτες
data Suit = Spades | Hearts deriving (Show)

data Rank = Ten | Jack | Queen | King | Ace deriving (Show)

type Card = (Rank, Suit)

type Hand = [Card]

value :: Rank -> Integer
value Ten = 1
value Jack = 2
value Queen = 3
value King = 4
value Ace = 5

cardValue :: Card -> Integer
cardValue (rank, suit) = value rank

-------------------------------------------------

-- πολυμορφική συνάρτηση
backwards :: [a] -> [a]
backwards [] = []
backwards (h : t) = backwards t ++ [h]

-------------------------------------------------

-- πολυμορφικός τύπος δεδομένων
data Triplet a = Trio a a a deriving (Show)

-------------------------------------------------

-- Αναδρομικός τύπος δεδομένων
data Tree a = Children [Tree a] | Leaf a deriving (Show)

depth :: (Num p, Ord p) => Tree a -> p
depth (Leaf _) = 1
depth (Children c) = 1 + maximum (map depth c)

Τύποι ορισμένοι από τον χρήστη

-- ο κώδικας βρίσκεται στο αρχείο day3.hs
data Suit = Spades | Hearts deriving (Show)
data Rank = Ten | Jack | Queen | King | Ace deriving (Show)
type Card = (Rank, Suit)
type Hand = [Card]

Αναδρομικοί τύποι

-- ο κώδικας βρίσκεται στο αρχείο day3.hs
data Tree a = Children [Tree a] | Leaf a deriving (Show)
ghci> let tree = Children[Leaf 1, Children [Leaf 2, Leaf 3]]
ghci> tree
Children[Leaf 1, Children [Leaf 2, Leaf 3]]

Μεταγλώττιση και εκτέλεση κώδικα

example1.hs
1
2
3
4
5
6
fac :: (Eq p, Num p) => p -> p
fac 0 = 1
fac n = n * fac (n-1)

main :: IO ()
main = print (fac 42)

Εκτέλεση από τον διερμηνευτή ghci

$ ghci example1.hs
GHCi, version 9.4.8: https://www.haskell.org/ghc/  :? for help
[1 of 2] Compiling Main             ( example1.hs, interpreted )
Ok, one module loaded.
ghci> main
1405006117752879898543142606244511569936384000000000

Μεταγλώττιση από το ghc, εκτέλεση

$ ghc example1.hs
[1 of 2] Compiling Main             ( example1.hs, example1.o )
[2 of 2] Linking example1
$ ./example1
1405006117752879898543142606244511569936384000000000

Μεταγλώττιση και εκτέλεση με το runghc

$ runghc example1.hs
1405006117752879898543142606244511569936384000000000

Ανάγνωση και εγγραφή σε αρχεία

Ανάγνωση από το αρχείο κειμένου input_for_haskell.txt

fileReadFrom.hs
1
2
3
4
5
6
main :: IO()
main = do
   content <- readFile "input_for_haskell.txt"
   let linesOfFiles = lines content
       x = map words linesOfFiles
   print x

Μεταγγλώττιση και εκτέλεση:

$ ghc fileReadFrom.hs
[1 of 2] Compiling Main             ( fileReadFrom.hs, fileReadFrom.o )
[2 of 2] Linking fileReadFrom
$ ./fileReadFrom
[["maria","8"],["petros","9"],["kostas","5"]]

Εγγραφή περιεχομένων λίστας στο αρχείο κειμένου "output_from_haskell.txt"

fileWriteTo.hs
1
2
3
4
5
main :: IO ()
main = do
  let a_list = ["Arta", "Informatics", "Telecommunications"]
  writeFile "output_from_haskell.txt" (unlines a_list)
  print "Write successed!"

Μεταγγλώττιση και εκτέλεση:

$ ghc fileWriteTo.hs
[1 of 2] Compiling Main             ( fileWriteTo.hs, fileWriteTo.o )
[2 of 2] Linking fileWriteTo
$ ./fileWriteTo
"Write successed!"

Το αρχείο output_from_haskell.txt αποθηκεύεται στον τρέχοντα φάκελο.

Βιβλία - σημειώσεις

Videos

Μαθήματα

Haskell documentation

Πηγές