Tipovi
Tip predstavlja kolekciju vrednosti. Svaka vrednost u Haskel jeziku, pripada jednom i samo jednom, tipu. Činjenicu da neka vrednost v
pripada tipu T
označavamo sa v :: T
. Izraz v :: T
često čitamo kao "v
poseduje tip T
".
Haskel poseduje statički sistem tipova što znači da su tipovi svih izraza poznati prilikom kompilacije (ili interpretacije). Time se otklanjaju mnoge greške koje bi mogle srušiti ceo program prilikom izvršavanja1. Jezici poput Python-a ili JavaScript-a nemaju statički sistem tipova, već dinamički sistem tipova. U takvim jezicima, tipovi izraza se određuju prilikom izvršavanja programa što ostavlja veliku mogućnost za pojavljivanje bagova.
Još jedna karakteristika koja odlikuje Haskelov sistem tipova je zaključivanje tipova. Dok je u jezicima poput C-a ili Jave (koji su statički tipizirani kao i Haskel) neophodno navesti tip svake promenljive ili funkcije, Haskel kompajler (skoro) uvek može zaključiti tip izraza bez potrebe da programer eksplicitno navede tip2. Po ovome se Haskel značajno razlikuje od ostalih jezika sa statičkim sistemom tipova. Statički tipizirani jezici poput Jave i C++-a zahtevaju od programera da eksplicitno navede tip (većine) vrednosti.
Tipovi čine značajan deo Haskel jezika, i neophodno je dobro razumeti ovaj sistem. U nastavku ove lekcije upoznaćemo se sa nekim osnovnim tipovima u Haskelu, kao i operacijama i funkcijama koje se mogu koristiti sa ovim tipovima.
Tip Bool
Tip Bool
predstavlja kolekciju koja sadrži dve logičke vrednosti: True
i False
3.
Bool
.Nad logičkim vrednostima moguće je vršiti naredne logičke operacije:
- Konjunkcija (i)
p && q
dve logičke vrednostip
iq
je logička vrednost koja ima vrednostTrue
ako logičke vrednostip
iq
imaju vrednostTrue
. - Disjunkcija (ili)
p || q
dve logičke vrednostip
iq
je logička vrednost koja ima vrednostTrue
ako barem jedna od vrednostip
iq
ima vrednostTrue
.
Koristeći ove dve operatore i zagrade, možemo kreirati logičke izraze proizvoljne složenosti.
ghci> True || False True ghci> ((True && False) || False) && (True || True) False
Uz navedene operacije koristimo i funkciju not
koja negira logičku vrednost4. Ovde je dobro mesto da kažemo par reči o funkcijama u Haskelu, jer funkcije predstavljaju osnovni koncept ovog jezika.
Funkcije su vrednosti koje "transformišu" neke druge vrednosti, odnosno od nekih vrednosti prave nove vrednosti. Na primer, sa imenom not
je označena funkcija koja negira logičku vrednost koja joj je prosleđena, tj. True
"pretvara" u False
a False
u True
. Da bi smo "pozvali" funkciju not
potrebno je da nakon imena funkcije napišemo logičku vrednost koju želimo da negiramo. Za razliku od drugih jezika, u Haskelu ime funkcije i argument razdvajamo razmakom, a ne zagradom:
ghci> not True False ghci> a = True && False ghci> not a True
not
možemo pozivati nad logičkim vrednostima ili nad imenima kojima su dodeljene logičke vrednosti (u ovom primeru to je ime a
).Funkcija not
vraća novu logičku vrednost, te stoga tu vrednost možemo kombinovati ponovo u složenijim logičkim izrazima:
ghci> (not True) || False False
Zagrade postavljamo oko izraza da bismo uspostavili poredak kojim je potrebno evaluirati izraz. U prethodnom primeru postavili smo zagrade oko logičkog izraza not True
, da bismo bili sigurni da se celokupan izraz neće protumačiti kao not (True || False)
5. Ali kao što u aritmetici imamo konvenciju o prioritetu računskih operacija, tako i u Haskelu imamo niz pravila koja se tiču prioriteta raznih operatora i poziva funkcija. Za sada je važno znati da primena funkcije na vrednost ima najveći prioritet. Prema tome, izraz not True || False
se tumači kao (not True) || False
, baš kao što se se aritmetički izraz \(5\cdot 4 + 7\) tumači kao \((5\cdot 4) + 7\) a ne kao \(5 \cdot (4 + 7)\).
Osim logičkih operatora, logičke vrednosti možemo da poredimo sa operatorima ==
i /=
, koji utvrđuju da li su vrednosti iste odnosno različite. Rezultati ovih poređenja su takođe logičke vrednosti:
ghci> p = True ghci> q = False ghci> p == p True ghci> p == q False ghci> p /= q True
Tipovi Int i Integer
Tip Int
predstavlja kolekciju celobrojnih vrednosti koje se mogu predstaviti u procesorskoj reči, odnosno u 32 ili 64 bita6.
Nad vrednostima tipa Int
moguće je vršiti naredne operacije i funkcije:
+
i *
Sa sabiranjem i množenjem brojeva smo se već upoznali u prethodnoj lekciji. Ovi operatori se ponašaju isto kao i svim drugim programskim jezicima ili u svakodnevnoj aritmetici.
-
Oduzimanje brojeva je takođe poznato iz svakodnevnog života i ostalih programskih jezika. Ali potrebno je naglasiti da se sa znakom -
može negirati broj. Znak -
ima nizak prioritet, te je često potrebno koristiti zagrade oko izraza koji se negira:
ghci> 3 * -2 <interactive>:1:1: error: precedence parsing error cannot mix ‘*’ [infixl 7] and prefix `-' [infixl 6] in the same infix expression ghci> 3 * (-2) -6
3 * -2
dovodi do greške u parsiranju, te je potrebno staviti zagrade oko broja koji se negira.div
Za deljenje celobrojnih vrednosti, koristi se funkcija div
od dve promenljive. Prvi parametar je deljenik (broj koji se deli) a drugi je delilac (broj kojim se deli). Rezultat deljenja je celobrojna vrednost odnosno vrednost tipa Int
.
I pri radu sa funkcijama više promenljivih argumenti se razdvajaju razmacima7:
ghci> div 7 2 3
Ponovo napominjemo da primena funkcije na argument ima najveći prioritet. Stoga se kôd div 7 2 * 2
interpretira kao (div 7 2) * 2
a ne kao div 7 (2 * 2)
.
mod
Funkcija dva argumenta mod
vraća ostatak pri celobrojnom deljenju prvog argumenta sa drugim.
ghci> mod 7 3 1
Za funkcije div
i rem
i proizvoljne cele brojeve x
i y
različite od nule važi veza \[\mathtt{(div\; x\; y)*y + (mod\; x \; y) = x}.\]
^
Operatorom ^
je moguće stepenovati celobrojnu vrednost na prirodan stepen. Na primer 7^2
će se evaluirati u 49
.
Ako pokušamo da sa ^
stepenujemo broj na negativan stepen dobićemo izuzetak8:
ghci> 7^(-2) *** Exception: Negative exponent
Int
Poređenja
Vrednosti tipa Int
takođe možemo porediti sa operatorima ==
i /=
. Ali možemo koristiti i operatore <
, <=
, >
i >=
koji upoređuju brojeve po veličini. Rezultat svih navedenih operacija je logička vrednost:
ghci> 6 == 3 * 2 True ghci> 2 < 3 True ghci> 2 >= 3 False
U Haskelu nije dozvoljeno porediti tipove različitog tipa sa operatorima ==
, /=
, <
, <=
, >
i >=
. U jezicima poput Javaskripta, poređenje poput True == 1
bi bilo validno (i tačno), dok u Haskelu takvo poređenje dovodi do greške:
ghci> True == 1 <interactive>:1:9: error: • No instance for (Num Bool) arising from the literal ‘1’ • In the second argument of ‘(==)’, namely ‘1’ In the expression: True == 1 In an equation for ‘it’: it = True == 1
Tip Integer
Integer
je takođe tip koji predstavlja celobrojne vrednosti. I nad ovim vrednostima ovog tipa mogu se koristiti funkcije +
, -
, *
, div
, mod
, rem
, ^
, ==
, /=
, <
, <=
, >
i >=
. Razlika između tipova Int
i Integer
je u tome što su vrednosti tipa Integer
neograničene9, dok su vrednosti tipa Int
ograničene dužinom procesorske reči.
Da bi demonstrirali razliku možemo napraviti mali eksperiment. U jednoj .hs
datoteci definišimo jednu Int
i jednu Integer
konstantu:
a :: Int a = 7 b :: Integer b = 7
Nakon učitavanja gorenavedenog koda, možemo stepenovati definisane vrednosti na velike stepene i tako prekoračiti ograničenje koje nameće Int
tip. Uvidećemo da su rezultati različiti:
ghci> a^70 254007274765394321 ghci> b^70 143503601609868434285603076356671071740077383739246066639249
b^70
daje tačnu vrednost.Pri radu sa tipom Integer
trebalo bi imati na umu da su aritmetičke operacije nad ovim tipom značajno sporije nego nad Int
tipom. Razlog tome je što se aritmetičke operacije sa Int
odbijaju na nivou hardvera, što nije slučaj i sa tipom Integer
.
Tipovi Float i Double
Za reprezentovanje realnih brojeva koriste se tipovi Float
i Double
koji realan broj prezentuju u skladu sa IEEE 754 standardom. Jedina razlika između ova dva tipa je u tome što Float
koristi jednostruku tačnost (32 bita) a Double
dvostruku tačnost (64 bita). Kako moderne arhitekture računara podjednako dobro podržavaju računske operacije u jednostrukoj i dvostrukoj tačnosti, preporučeno je da se uvek koristi Double
.
Sa tipovima Float
i Double
moguće je koristiti operatore +
, -
, *
, ^
, ==
, /=
, <
, <=
, >
i >=
. Za deljenje realnih brojeva koristi se operator /
. Uz operator ^
, za stepenovanje se može koristiti i operator **
koji dozvoljava da stepen bude proizvoljan realan broj10
Takođe, Haskel ima ugrađene funkcije poput sin
, cos
, tan
, asin
, acos
, atan
, exp
, log
, sqrt
itd... Dostupna je takođe i Arhimedova konstanta \(\pi\) pod imenom pi
.
ghci> r = 10 ghci> pi * r**2 314.1592653589793
Liste
U Haskelu liste su strukture podataka koje predstavljaju uređenu kolekciju elemenata nekog tipa. U jednoj listi se ne mogu naći dve vrednosti različitog tipa.11
U kodu, lista se konstruiše navođenjem vrednosti između uglastih zagrada, međusobno razdvojih zarezima. Na primer, lista od prva četiri prirodna broja može da se definiše kao [0,1,2,3]
. Razmaci oko elemenata i zagrada nisu od bilo kakvog značaja.12 Prazna lista se zapisuje kao []
.
[Bool]
sadrži beskonačno mnogo vrednosti, čak i ako tip Bool
sadrži samo dve.Za označavanje tipa liste koristi se specijalna sintaksa. Ako je data lista elemenata tipa a
tada ta lista ima tip koji se označava sa [a]
13. Na primer, lista elemenata tipa Int
poseduje tip [Int]
.
maliProstiBrojevi :: [Int] maliProstiBrojevi = [2,3,5,7,11,13,17,19] logickeVrednosti :: [Bool] logickeVrednosti = [True,False] vazneKonstante :: [Double] vazneKonstante = [3.14159,2.71828,0.57721]
U specijalnom slučaju, možemo definisati i listu lista, i listu lista lista, itd...
a :: [[Int]] a = [[1], [2,3], [4,5,6]]
a
je lista lista celih brojeva. Tip ove liste je [[Int]]
. Primetimo, da se u listi tipa [[Int]]
ne može naći element 5
(jer je to vrednost tipa [Int]
), ali može element [5]
koji je tipa [5]
.Zadatak 1. Koliko elemenata ima lista [[]]
?
Lista [[]]
ima jedan element. Taj element je prazna lista []
.
Liste možemo spajati uz pomoć operatora ++
. Nadovezivanjem prazne liste []
na početak ili kraj liste, ta lista se ne menja. Naravno, moguće je spajati samo liste istog tipa.
ghci> [1,2,3,4] ++ [10,11,12] [1,2,3,4,10,11,12] ghci> [True,False] ++ [] [True,False]
ghci> [1,2,3] ++ [True] <interactive>:1:2: error: • No instance for (Num Bool) arising from the literal ‘1’ • In the expression: 1 In the first argument of ‘(++)’, namely ‘[1,2,3]’ In the expression: [1,2,3] ++ [True]
Za nadovezivanje elemenata na početak liste može se koristi i operator :
. Sa leve strane operatora :
potrebno je navesti vrednost tipa a
, a sa desne strane je potrebno navesti listu elemenata tipa [a]
ghci> 1 : [2,3,4,5] [1,2,3,4,5] ghci> [1] ++ [2,3,4,5] [1,2,3,4,5]
ghci> [1] : [2,3,4,5] <interactive>:1:1: error: • Non type-variable argument in the constraint: Num [a] (Use FlexibleContexts to permit this) • When checking the inferred type it :: forall a. (Num a, Num [a]) => [[a]] ghci> 1 ++ [2,3,4,5] <interactive>:2:1: error: • Non type-variable argument in the constraint: Num [a] (Use FlexibleContexts to permit this) • When checking the inferred type it :: forall a. (Num a, Num [a]) => [a]
++
ili da se liste spajaju pomoću operatora :
.Liste predstavljaju strukturu podataka koja se najčešće koristi u Haskelu. Zbog toga postoje mnoge funkcije koje uzimaju ili vraćaju liste. Sa mnogim takvim funkcijama upoznaćemo se u lekciji Liste. Za sada navodimo samo naredne funkcije:
length
vraća dužinu prosleđene liste.head
vraća prvi element prosleđene liste ako taj element postoji. Pozivanjem ove funkcije nad praznom listom dolazi do izuzetka.last
vraća poslednji element prosleđene liste ako taj element postoji. Pozivanjem ove funkcije nad praznom listom dolazi do izuzetka.init
vraća sve elemente prosleđene liste osim poslednjeg. Pozivanjem ove funkcije nad praznom listom dolazi do izuzetka.tail
vraća sve elemente prosleđene liste osim prvog. Pozivanjem ove funkcije nad praznom listom dolazi do izuzetka.reverse
vraća prosleđenu listu u obrnutom smeru.elem
je funkcija od dva parametara koja proverava da li se prvi argument nalazi u listi prosleđenoj kao drugi argument. Rezultat provere je logička vrednost.null
proverava da li prosleđena lista prazna. Rezultat provere je logička vrednost.
ghci> mojaLista = [10,20,30,40,50,60] ghci> length a 6 ghci> head a 10 ghci> last a 60 ghci> init a [10,20,30,40,50] ghci> tail a [20,30,40,50,60] ghci> reverse a [60,50,40,30,20,10] ghci> elem 10 a True ghci> null [10, 9, 8, 7] False
Sa operatorom !!
možemo pristupiti \(m\)-tom elementu liste. Sa leve strane operatora se navodi lista, a sa desne strane vrednost tipa Int
. Elementi su indeksirani od nule. Pokušaj pristupanja indeksu koji je veći od dužine liste dovodi do izuzetka.
ghci> [True, False, True, False, True] !! 2 True
ghci> [True, False, True, False, True] !! 7 *** Exception: Prelude.!!: index too large
Kad god imamo tip čije se vrednosti mogu upoređivati sa ==
, /=
, <
, <=
, >
i >=
tada se i liste elemenata tog tipa mogu porediti sa istim operatorima. Ova poređenja se vrše član po član. Specijalno, liste se su jednake ako i samo ako sadrže iste vrednosti poređane istim redosledom.
ghci> [1,2,3] == [1,2,3] True ghci> [1,2,3] == [2,3,1] False
Karakteri i niske
Vrednosti tipa Char
su karakteri kodirani Unicode standardom. U samom kodu, karakteri se navode između jednostrukih navodnika:
ghci> mojKarakter = 'ш' ghci> mojKarakter 'ш'
Funkcija fromEnum
konvertuje karakter u vrednost tipa Int
koja predstavlja poziciju tog karaktera u Unicode kodnoj tabeli
ghci> fromEnum 'A' 65
'A'
nalazi tek na 65 mestu. Na prvim mestima se nalaze specijalni karakteri, zatim cifre, zatim velika pa mala latinična slova, nakon toga slede interpunkcijski znaci, pa tek onda nelatinična pisma...Karaktere, kao i brojeve, možemo porediti. To poređenje je takođe ustanovljeno pozicijom u Unicode kodnoj tabeli. Stoga imamo
ghci> 'a' < 'b' True ghci> 'a' < 'B' False ghci> 'a' < 'ш' True
Terminom niske nazivamo liste karaktera. Niske se mogu koristiti za prezentovanje tekstualnih vrednosti14. Na primer, pozdrav Hello! se u Haskelu predstavlja kao lista ['H', 'e', 'l', 'l', 'o', '!']
. Srećom, niske se mogu zapisati i prostim navođenjem karaktera između dvostrukih navodnika potpuno isto sintaksi u ostalim programskim jezicima. Tako se niska Hello! može konstruisati i kao "Hello!"
:
ghci> "Hello" == ['H', 'e', 'l', 'l', 'o', '!'] True
Kako su niske u suštini liste, sve funkcije nad listama možemo iskoristiti i za niske.
ghci> niska = "Hello" ++ "!" ghci> niska "Hello!" ghci> length a 6 ghci> head a 'H' ghci> last a '!' ghci> init a "Hello" ghci> tail a "ello!" ghci> reverse a "!olleH" ghci> null "" True
head
vraća prvi element liste, a u ovom primeru to je karakter 'a'
(a ne niska "a"
). Karakteri se uvek navode između jednostrukih navodnika, a niske između dvostrukih.Mnogi tipovi u Haskelu (ali svakako ne svi), dozvoljavaju da se nad njima pozove funkcija show
. Ova funkcija uzima vrednost i vraća nisku koja prezentuje tu vrednost. Na primer:
ghci> show 2 "2" ghci> show True "True" ghci> show [1, 2, 3] "[1,2,3]"
"2"
nije isto što i vrednost 2
, kao što ni logička vrednost True
i niska "True"
nisu isto, itd... Ako je neke vrednosti moguće "prikazati" sa show
funkcijom, tada je i listu tih vrednosti moguće "prikazati" sa show
funkcijom. Lista će uvek biti prikazana na kanonski način (bez razmaka između elemenata), bez obzira kako smo tu listu konstruisali u kodu.Uređene \(n\)-torke
Liste sadrže proizvoljan broj elemenata istog tipa. Suprotno tome, uređene \(n\)-torke sadrže fiksiran broj elemenata ne nužnog istog tipa. Uređene \(n\)-torke se konstruišu navođenjem vrednosti odvojenih zarezima između zagrada ( )
. Tip uređene \(n\)-torke je uređena \(n\)-torka odgovarajućih tipova.
Vrednost ("Pozdrav!", True, 100.0)
je jedna uređena trojka koja sadrži nisku, logičku vrednost i Float
. Ova uređena trojka ima tip ([Char], Bool, Float)
.
(A, B)
možemo elegantno predstaviti u pravugaonom rasporedu u kom vrste odgovaraju tipu A
a kolone tipu B
. U ovom slučaju broj kolona (a i vrsta) je izuzetno veliki, pa ne možemo predstaviti sve vrednosti na ilustraciji.Najznačajnije uređene \(n\)-torke su svakako uređene dvojke koje češće nazivamo uređeni parovi. Nad svakim uređenim parom možemo pozvati funkciju fst
i snd
koje redom vraćaju prvu i drugu koordinatu uređenog para.
ghci> fst (5, 'C') 5 ghci> snd (5, 'C') 'C'
fst
i snd
se mogu koristiti za "izvlačenje" prve i druge koordinate uređenog para.Uređene trojke, četvorke, itd. se ređe koriste, te za njih ne postoje funkcije poput fst
i snd
. U narednim sekcijama ćemo videti kako možemo sami konstruisati odgovarajuće funkcije.
Poredak tipova u tipu uređene \(n\)-torke je važan: tip (Int, Char)
nije isti kao tip (Char, Int)
. Takođe broj pojavljivanja nekog tipa uređenoj \(n\)-torci je bitan: tip (Int, Int, Char)
nije isti kao tip (Int, Char)
.
Zadatak 2. Koliko vrednosti sadrži tip (Bool, Bool)
?
Navedeni tip sadrži četiri vrednosti: (True, True)
, (True, False)
, (False, True)
i (False, False)
.
Zadatak 3. Ako tipovi A1
, A2
... Ak
sadrže redom \(n_1\), \(n_2\) ... \(n_k\) vrednosti, koliko vrednosti sadrži tip (A1, A2, ...., Ak)
?