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 zaustaviti program prilikom izvršavanja ili dovele do pogrešnog rezultata1. 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 većine ostalih jezika sa statičkim sistemom tipova.
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 False3.
Bool.Nad logičkim vrednostima moguće je vršiti naredne logičke operatore (operacije):
- Konjunkcija (i)
p && qdve logičke vrednostipiqje logička vrednost koja ima vrednostTrueako logičke vrednostipiqimaju vrednostTrue. - Disjunkcija (ili)
p || qdve logičke vrednostipiqje logička vrednost koja ima vrednostTrueako barem jedna od vrednostipiqima vrednostTrue.
Koristeći navedene 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 operatore 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 od vrednosti "prave" nove vrednosti. Na primer, imenom not označena je funkcija koja negira logičku vrednost koja joj je prosleđena, tj. od True "pravi" u False a od False "pravi" True. Da bismo "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 aritmetički izraz \(5\cdot 4 + 7\) tumači kao \((5\cdot 4) + 7\) a ne kao \(5 \cdot (4 + 7)\).
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 sa dva parametra. 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 parametara, 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 mod vraća ostatak pri celobrojnom deljenju prvog argumenta sa drugim.
ghci> mod 7 3 1
Za funkcije div i mod 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 se evaluira u 49.
Ako pokušamo da sa ^ stepenujemo broj na negativan stepen dobićemo izuzetak8:
ghci> 7^(-2) *** Exception: Negative exponent
IntIzuzetak je posebna vrednost koja označava da je došlo do neke greške prilikom izvršavanja programa. Pojava izuzetka dovodi do prekida izvršavanja (interpretacije) programa. Mnogo više o izuzecima biće rečeno kasnije.
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 == 5 False 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 aritmetičke operacije sa vrednostima tipa Int odgovaraju procesorskim instrukicjama, što nije slučaj i sa vrednostima tipa Integer.
Tipovi Float i Double
Za reprezentovanje realnih brojeva koriste se tipovi Float i Double koji realan broj predstavljaju 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 razdvojenih 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] logičkeVrednosti :: [Bool] logičkeVrednosti = [True,False] važneKonstante :: [Double] važneKonstante = [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]] b :: [[[Int]]] b = [a, [[100], [200]], []]
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:
lengthvraća dužinu liste.headvraća prvi element liste ako taj element postoji. Pozivanjem ove funkcije nad praznom listom dolazi do izuzetka.lastvraća poslednji element liste ako taj element postoji. Pozivanjem ove funkcije nad praznom listom dolazi do izuzetka.initvraća sve elemente liste osim poslednjeg. Pozivanjem ove funkcije nad praznom listom dolazi do izuzetka.tailvraća sve elemente liste osim prvog. Pozivanjem ove funkcije nad praznom listom dolazi do izuzetka.reversevraća prosleđenu listu u obrnutom smeru.elemje 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.nullproverava da li je lista prazna. Rezultat provere je logička vrednost.
ghci> mojaLista = [10,20,30,40,50,60] ghci> length mojaLista 6 ghci> head mojaLista 10 ghci> last mojaLista 60 ghci> init mojaLista [10,20,30,40,50] ghci> tail mojaLista [20,30,40,50,60] ghci> reverse mojaLista [60,50,40,30,20,10] ghci> elem 10 mojaLista True ghci> elem 100 mojaLista False ghci> null False ghci> null [] True
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, a u slučaju da je jedna od lista kraća od druge, tada se ta lista smatra strogo manjom (tj. manjom u smislu operatora <). 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
ghci> [1, 2] < [1, 3] True
2 < 3 zaključujemo da je leva lista strogo manja.ghci> [1, 2] < [1, 2, 3] True
ghci> [1, 2, 3, 100] < [1, 2, 4, 5] True
3 < 4, leva lista se smatra manjom. Primetimo da je leva lista manja od desne čak iako je četvrti član leve liste značajno veći od četvrtog člana desne liste. Dakle, prilikom poređenja, razmatra se samo prvo mesto na kom se liste razlikuju, dok naredne vrednosti ne utiču na poredak.ghci>[] < [1] True
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 analogno sintaksi u ostalim programskim jezicima. Tako se niska Hello! može konstruisati i kao literal15 "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 niska 6 ghci> head niska 'H' ghci> last niska '!' ghci> init niska "Hello" ghci> tail niska "ello!" ghci> reverse niska "!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"
"2" i vrednost 2 nisu isto, kao što ni logička vrednost True i niska "True" nisu isto, itd...ghci> show [1, 2, 3] "[1,2,3]"
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 A₁, A₂ ... Aₙ sadrže redom \(k_1\), \(k_2\) ... \(k_n\) vrednosti, koliko vrednosti sadrži tip (A₁, A₂, … , Aₙ)?