You are on page 1of 53

Funkcionālā programmēšana

Programmēšanas valodas
Funkcionālā programmēšana: ievads
• Funkcionālās programmēšanas sākumi:  - rēķini (1935)

• Vēsturiski pirmās funkcionālās programmēšanas valoda:


LISP 1960 g., variācijas: SCHEME, Common Lisp

• Pamatideja: programma ir funkcija, kas ieejas datus attēlo par izejas datiem;

• Funkcija - tāds pats datu objekts, kā pārējie, var definēt funkcijas, kas funkcijas
attēlo par funkcijām. Augsta modularitāte;

• HASKELL – intepretatīva, interaktīva vide, funkcionālām valodām pastāv arī


kompilatori;

• Valodai HASKELL ir stingra tipu sistēma (SCHEME – dinamiska tipu sistēma);

• Citas funkcionālas valodas: ML, MIRANDA, Lazy ML, F#, Clojure, u.c.

• Funkcionālās programmēšanas sākotnējie lietojumi:


prototipu veidošana, mākslīgais intelekts, automātiskas pierādīšanas sistēmas, citi
matemātiski lietojumi

• Mūsdienās nopietni papildina objekt-orientēto programmēšanas stilu (C#, SCALA,


JAVA, ..)
Funkcionālā programmēšana: raksturojums
Funkcionālais programmēšanas stils “tīrā formā”:
• Funkcijas skaidri nodala ienākošās vērtības (argumentus) no izejošajām vērtībām
(rezultātiem).
• Nav: mainīgie (atmiņas šūnai piesaistīti vārdi) un piešķiršanas operatori (ir parametri
procedūrām).
• Nav: cikli (ir rekursīvi izsaukumi).
• Funkcijas vērtība: atkarīga no argumentu vērtībām, nevis no funkcijas aprēķināšanas
kārtības, vai no izpildes ceļa, kas novedis līdz funkcijas izsaukumam.
• Funkcijas kā dati: pilnvērtīgas (“pirmās šķiras”) vērtības.
Valodās, kas ir pamatā funkcionālas, var būt pieejamas arī īpašības, kas atkāpjas no šī
stila.
Kas funkcionālajā programmēšanā ir:
• Funkciju kompozīcija
• Vispārīgas (augstākas kārtas) funkcijas, kuru argumenti arī ir funkcijas
• Aizturētā izpildes paradigma (daļā valodu) – vērtību rēķina tikai, kad tā nepieciešama
Sekas: var darbības definēt bezgalīgu objektu līmenī (kā matemātiskā notācijā).
Stiprās un vājās puses
Stiprās puses:
- programmas tiek uniformi aplūkotas kā funkcijas;
- funkcijas tiek uztvertas kā pilnvērtīgi dati;
- blakus efektu ierobežošana (HASKELL – nav blakus efektu)
- automātiska atmiņas pārvaldība
- elastīga, īsa notācija, vienkārša semantika

Iespējamas vājās puses:

- var būt realizācijas neefektivitāte


(sasniegumi funkcionālo valodu kompilācijas un interpretācijas jomā ļauj izmantot
funkcionālās valodas situācijās, kurās efektivitāte nav galvenais mērķis)

- notācija / jēdzienu sistēma / programmas organizācija var būt grūti uztverama


izmanto augstāku abstrakcijas līmeni, nekā imperatīvās un objektorientētās
valodas (šī var būt arī priekšrocība)

- nav labu līdzekļu virknē paveicamu darbību aprakstam.

Funkcionālā programmēšana:
1) iespējams rakstīt nopietnas lietotnes
2) funkcionālajās valodās ietvertajām idejām liela ietekme uz programmēšanas
valodu attīstību vispār.
- rēķini
- termi: M ::= x |x.M | M N
x - mainīgais
x.M - abstrakcija (funkcija x  M, parasti M = M(x))
M N - aplikācija: terms - funkcija M tiek pielietots argumenta termam N
Piemērs: funkciju x  x2 apzīmē kā x.x2 [x2, +, - tiks ieviesti vēlāk]
Vairākargumentu funkcijas: x. (y. (z. M)), saīsināti pieraksta: x y z. M
Funkciju pielietošana vairākiem argumentiem: M N1 N2 N3 apzīmē (((M N1 ) N2 ) N3 )
Precedence: x.yx  x.(y x), nevis (x.y) x (aplikācija saista ciešāk)
Funkciju izpilde  - rēķinos:  - redukciju virkne.
 - redukcija - funkciju pielieto argumentam: (x.x2) 7  72
(x.x2) (13-z)  (13-z)2
Pareiza arī šāda “bezjēdzīga” redukcija: (x.x2) (xy.x+y)  (xy.x+y)2
x.M N  M[N/x] (x.M N tiek reducēts par reducēts par termu M, kurā visās
mainīgā x brīvās ieiešanas vietās tiek ierakstīts terms N).
Nepieciešamības gadījumā tiek izmantota arī  - redukcija: saistīto mainīgo
pārsaukšana: x.x2 var tikt reducēts par y.y2.
Terms ir normālā formā, ja tam nevar pielietot  - redukciju.
- rēķini: redukcija
(xy.x+y) 5  y.x+y [5/x]  y.5+y - funkcijas pielietošana vienam argumentam
(xy.x+y) 5 7  ( x.(y.x+y) 5) 7  (y.5+y) 7  5+7
 - redukcija pielietota terma iekšienē
 - terms ir koks, virsotnes: x, , @ (apzīmē aplikāciju):

@ @
@ 7 @ 7 @ @
 5  5   7  7  5+7
x  x  y 5+y y 5+y
y x+y y x+y

- terma normālā forma (vērtība): nav iespējamas tālākas  - redukcijas (aprēķini
pabeigti, visas funkcijas pielietotas saviem argumentiem)
Ja - termam ir normālā forma, tad tā ir viena vienīga (redukciju secība var būt
dažāda, iespējami dažādi izpildes scenāriji, iespējams veidot “paralēlismu”).
Uzrakstīt  - rēķinu interpretatoru - ? [,  - redukcijas]
Funkcionālas valodas realizācija: ērti attēlot  - termus, efektīvi realizēt redukciju.
- rēķini: redukcija un normālā forma
Teorēma. Ja  - termam ir normālā forma, tad tā ir viena vienīga.
Ilustrācija:
 f.  x. (f (f x))  y.(y + y) 7   x. ( ( y. (y + y)) ( ( y. (y + y)) x)) 7

(Funkcijas termā parametra f vietā ievietota ievietota vērtība  y.(y + y) ).


3 iespējami nākamie soļi redukcijai:
 ( y. (y + y)) ( ( y. (y + y)) 7)  ... (x vietā ievieto 7)

 x. ( y. (y + y)) ( x + x ) 7  ... (y vietā ievieto x)

  x. ( y. (y + y)) ( ( z. (z + z)) x) 7
  x. (( z. (z + z)) x) + (( z. (z + z)) x) 7  ...

Rezultāts (normālforma): (7 + 7) + (7 + 7), visos gadījumos.


Ja + operācija arī būtu izpildāma, tad rezultējošā normālforma būtu 28.
 - terms, kam nav normālās formas:
  (x.xx) (x.xx) ,       …
- rēķini: dažādas redukcijas stratēģijas
 - terms, kam nav normālās formas:
  (x.xx) (x.xx) ,       …
Dažādu redukcijas stratēģiju nozīme:
K = xy.x, I = x.x, K I   I , bet arī, ja izvēlamies vispirms reducēt simbolu ,
iegūstam K I   K I   K I   …
Teorēma. “Kreisā” redukcijas stratēģija vienmēr atrod normālformu, ja tāda eksistē.
Kreisā redukcijas stratēģija funkcionālās programmēšanas terminoloģijā:
f ( g (x) ): rēķina f (?), aprēķina tik daudz no argumenta, cik nepieciešams.
Slinkā (aizturētā) rēķināšana: Lazy evaluation.
let val x = 3 * 2 in (4 * 2) - (x + x) end
Būs divreiz jāaprēķina x vērtība (jo vērtība aprēķināta tad, kad vajadzīga)
Valodas HASKELL, MIRANDA, LAZY ML lieto Lazy Evaluation (izvairoties no
nepieciešamības divreiz aprēķināt vienas un tās pašas izteiksmes vērtību).
SCHEME, ML - pamatā argumentu aprēķina vispirms,
izņēmumi: loģisko izteiksmju un if-then-else izteiksmju aprēķināšana
(SCHEME ir arī aizturētās aprēķināšanas konstrukcijas)
- rēķini: naturālie skaitļi, operācijas
Naturālie skaitļi  - rēķinos: 0, 1, 2, 3, …, definē kodus:
c0   f x. x, c1   f x. (f x), c2   f x. f (f x), c3   f x. f ( f (f x)), …
 - terms F apraksta (daļēji definētu) funkciju f: N  N, ja n
*
F cn (, ) cm  f(n) = m
Teorēma. f: N  N ir aprakstāma ar  - termu tad un tikai tad, ja f ir izrēķināma uz
Tjūringa mašīnas. (Imperatīvās un funkcionālās programmēšanas “ekvivalence”)
Piemēri. Rakstam M = N, ja M un N reducējas par vienu normālo formu
(nejaukt ar M  N, kas apzīmē sintaktisku identitāti)
A+  xypq.xp(ypq), tad n, m A+ cn cm = cn + m
A*  xyz.x(yz), tad n, m A* cn cm = cn * m
Aexp  xy.yx, tad n, m, ja m1, tad Aexp cn cm = cn^m
true  xy.x, false  xy.y, if B then P else Q  B P Q
Pierādījumi: pašiem, vai sk. literatūrā par  - rēķiniem.
Piemērs: ( x y p q. x p (y p q)) ci cj   p q. ci p (cj p q) 
 p q. ( f x. (fi x)) p ( f x. (fj x) p q)   p q.  x. (pi x) (pj q) 
 p q. pi (pj q)   p q. pi+j q  ci+j
Funkcionālā programmēšana

Tēma: valoda Haskell

Programmēšanas valodas
Haskell instalācija un darba organizācija
http://www.haskell.org/
Jebkurš instalācijas veids būs piemērots/pietiekams
Programmas teksta rediģēšana - atsevišķā failā (rediģēšana ar parastu teksta
redaktoru, atsevišķiem redaktoriem var būt Haskell atslēgas vārdu atpazīšanas
serviss), interaktīvā vide – tikai izteiksmju aprēķināšana un rezultāta izdruka.
Teksta sākums: module nnn where
Izpildes vidē
:? piedāvā palīdzību (help)
definīciju ielāde ar :l[oad], (vai arī dbl-click uz faila pēc noklusēšanas to atver ar
ghci)
definīciju izmaiņu ielāde: :r[eload] (piemēram, ja failā kaut kas pamainīts)
: t eee parāda izteiksmes eee tipu
:set +t uzstāda režīmu pēc katras interaktīvajā vidē aprēķinātās
izteiksmes rādīt tās tipu
Vienkārša darba vide: http://replit.com/
Jālieto :load file.hs lai ielādētu funkciju definīcijas (lai varētu izpildīt Run komandu,
vajadzīgas tālākas konstrukcijas).
Valodas Haskell atbalsts ir arī Visual Studio Code.
Daži piemēri
No http://www.haskell.org/haskellwiki/Learn_Haskell_in_10_minutes
Prelude> 3 * 5
15
Prelude> 4 ^ 2 – 1
15
Prelude> (1 - 5)^(3 * 2 - 4)
16
Prelude> "Hello“
"Hello“
Prelude> "Hello" ++ ", Haskell“
"Hello, Haskell“
Prelude> succ 5 succ – standarta bibliotēkā iebūvēta funkcija
6
Prelude> round 6.59
7
Prelude> sqrt 2
1.4142135623730951
Prelude> not (5 < 3)
True
Prelude> gcd 21 14
7
Piemēri, ar tipu informāciju
Pēc izpildītas komandas :set +t
Prelude> 3 * 5
15
it :: Integer
Prelude> (1 - 5)^(3 * 2 - 4)
16
it :: Integer
Prelude> "Hello" ++ ", Haskell“
"Hello, Haskell“
it :: [Char] Simbolu virkne = simbolu saraksts
Prelude> sqrt 2
1.4142135623730951
it :: Double
Prelude> not (5 < 3)
True
it :: Bool
Tipi tiek noteikti automātiski (cik iespējams). Ja izteiksmei tips neeksistē – tipu kļūda.
Var būt situācijas, kad lietotājs tipu “saka priekšā” (arī dokumentācija).
Tālāki piemēri, tipu informācija
>5
5
it :: Integer
>5*5
25
it :: Integer
> 5.0
5.0
it :: Double
> 5 * 5.0
25.0
it :: Double Automātiska tipu konversija (šādi nav, piemēram, ML)
> 5 + ‘a’ Stingra tipu kontrole
No instance for (Num Char)
arising from the literal `5' at <interactive>:1:0
‘a’ ir ar tipu Char, 5 un + prasa, lai arī otrā argumenta tips piederētu tipu klasei Num
> :t (*)
(*) :: (Num a) => a -> a -> a
Arī funkcijām ir tipi, * var tikt pielietots jebkādam skaitliskam tipam
Anonīmās funkcijas (lambda – izteiksmes)
Izpildes vidē:
> (\x -> x*x) 5 Anonīmā funkcija (- izteiksme) un tās pielietojums
25
> (\x -> x*x) 5.1 Pielietojums cita tipa vērtībām
26.009999999999998
it :: Double

> :t (\x -> x*x)


(\x -> x*x) :: (Num a) => a -> a

Izteiksmēm (skalārām vērtībām, funkcijām, ...) var iedot arī vārdus ..


Piemēri: vārdi izteiksmēm
Izpildes vidē:
> (\x -> x*x) 5 Anonīmā funkcija (- izteiksmes) un tās pielietojums
25
Konkrēta vārda definīcija un vērtības piesaistīšana tam veicama programmas failā.
module aaa where module, where – atslēgas vārdi
three = 3
suc x = x + 1
inc = \x -> x+1
sq x = x * x
plus2 = (2 +)
times3 = (* 3)
Pēc faila atvēršanas:
> sq three
9
> :t sq
sq :: (Num a) => a -> a
> :t plus2
plus2 :: Integer -> Integer
(Num a) – tipu klase, norāda, ka a tips ir skaitlisks (Int, Integer, Float, Double, Rational)
Haskell piemēri, turpināts
fact 0 = 1
fact n = n * fact (n-1)
fact1 n = if n == 0 then 1 else n * fact1 (n-1)
square x = x * x
gcd1 u v = if v == 0 then u else gcd1 v (mod u v)
area a b c = let p = (a+b+c)/2 in sqrt(p * (p-a) * (p-b) * (p-c))
Funkciju definēšanā svarīgas atkāpes no kreisās puses (sk. fact piemēru)
Loģiskas izteiksmes, rēķināšana no kreisās puses
b1 = True || True
b2 = (2 == 3) && ((div 5 0)== 13)
b3 = ((div 5 0)== 13) && (2 == 3)
Funkciju kompozīcija: _ . _
> ( (* 3) . (2 +)) 5
21
Kompozīcija iespējama arī vārdā nosauktām funkcijām:
> (succ . succ) 4
6
Augstākas kārtas funkcijas
Funkciju tipi: a -> b
compose2 f x = f (f x) -- Funkcija, kuras arguments ir funkcija
> :t compose2
compose2 :: (t -> t) -> t -> t
sq x = x*x
> :t sq
sq :: (Num a) => a -> a
power4 = compose2 sq -- Pielietojam tikai vienam argumentam
> :t power4
power4 :: Integer -> Integer -- Izskatās mazliet dīvaini ..
power4a :: (Num a) => a -> a -- Šādi varam pateikt, kā jābūt.
power4a = compose2 sq
> :t power4a
power4a :: (Num a) => a -> a
Augstākas kārtas funkcijas – viens no funkcionālās programmēšanas “pamata
ieročiem”.
Haskell: Curry-Howard izomorfisms
Funkciju tipi: a -> b
Vairākargumentu funkcijas:
add :: (Int, Int) -> Int
add(x,y)= x+y
plus :: Int -> Int -> Int
plus x y = x + y
Iespējama konversija:
xcurry f x y = f(x,y) (xcurry add) ekvivalents ar plus
xuncurry f (x,y) = f x y (xuncurry plus) ekvivalents ar add
>:t xcurry
xcurry :: ((t, t1) -> t2) -> t -> t1 -> t2
> :t xuncurry
xuncurry :: (t -> t1 -> t2) -> (t, t1) -> t2
curry, uncurry – iebūvētās funkcijas Haskell standarta ievadā.
Saraksti: ievads
Saraksts – var uzglabāt vairākas viena tipa vērtības
Prelude> [1, 2, 3]
[1,2,3]
it :: (Num t) => [t]
Prelude> [1 .. 5]
[1,2,3,4,5]
Prelude> [1, 3 .. 10]
[1,3,5,7,9]
Prelude> [True, False, True]
[True,False,True]
Prelude> ['H', 'e', 'l', 'l', 'o'] Simbolu virkne – saraksts no Char elementiem
"Hello"
it :: [Char]
[] – tukšais saraksts
Prelude> 'C' : ['H', 'e', 'l', 'l', 'o'] Galvas elementa pievienošana
"CHello"
Izmantots http://www.haskell.org/haskellwiki/Learn_Haskell_in_10_minutes
Katru sarakstu var iegūt izmantojot [] un : .
Būtiski izmantots sarakstu apstrādes funkciju programmēšanā
Korteži
Kortežs – var uzglabāt fiksētu skaitu vērtību (piemēram 2), katrai var būt savs tips
Prelude> :t (1,True)
(1,True) :: (Num t) => (t,Bool)
Kortežu apstrādes funkcijas: fst, snd
Prelude> fst (1, 2)
1
Prelude> snd (1, 2)
2
Sarakstu pāri (divus sarakstus) par sarakstu no pāriem pārveido iebūvētā funkcija zip
Prelude> zip [1 .. 5] ['a' .. 'e']
[(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]
Prelude> :t zip
zip :: [a] -> [b] -> [(a,b)]
HASKELL: darbības ar sarakstiem

Katru sarakstu var iegūt izmantojot [] un : .


Būtiski izmantots sarakstu apstrādes funkciju programmēšanā
Saraksta garums length1 [] = 0
length1 (x:xs) = 1 + length1 xs
Konkatenācija: append [] x = x
append (h:t) x = h:(append t x)
«Atpako» pirmo argumentu, līdz sasniegts []
Darbības laiks – lineārs pēc pirmā argumenta.

append [1,2,3] [4,5] = append (1:[2,3]) [4,5] ->


1: (append [2,3] [4,5]) = 1: (append (2:[3]) [4,5]) ->
1: (2: (append [3] [4,5])) = 1: (2: (append 3:[] [4,5]))) ->
1: (2: (3: (append [] [4,5]))) ->
1: (2: (3: [4,5])) = [1,2,3,4,5]
++ - iebūvēta sarakstu konkatenācijas operācija, analoģiska append
HASKELL: Reverse

Reverse (kvadrātiskais laiks) reverse1 [] = []


reverse1 (h:t) = reverse1 t ++ [h]
reverse1 [1,2,3,4] = reverse1 (1:[2,3,4]) ->
(reverse1 [2,3,4]) ++ [1] = (reverse1 (2:[3,4])) ++ [1] ->
((reverse1 [3,4]) ++ [2]) ++ [1] = ((reverse1 (3:[4])) ++ [2]) ++ [1] ->
(((reverse1 [4]) ++ [3]) ++ [2]) ++ [1] = (((reverse1 (4:[])) ++ [3]) ++ [2]) ++ [1] ->
((((reverse1 []) ++ [4]) ++ [3]) ++ [2]) ++ [1] ->
((([] ++ [4]) ++ [3]) ++ [2]) ++ [1] -> .. -> 0 steps in append
(([4] ++ [3]) ++ [2]) ++ [1] -> .. -> 1 step in append
(([4,3]) ++ [2]) ++ [1] -> .. -> 2 steps in append
([4,3,2]) ++ [1] -> .. -> 3 steps in append
[4,3,2,1]

Reverse (lineārs laiks) rev2 ([], acc) = ([], acc)


rev2 (h:t,acc) = rev2(t,h:acc)
reverse2 x = snd(rev2(x,[])) Rekursīvā
reverse [1,2,3,4] -> «izvēršana» -
snd (rev2([1,2,3,4],[])) = snd (rev2(1:[2,3,4],[])) ->
snd (rev2([2,3,4],1:[])) = snd (rev2(2:[3,4],[1])) ->
izmanto bagātīgāku
snd (rev2([3,4],2:[1])) = snd (rev2(3:[4],[2,1])) -> datu struktūru, kā
snd (rev2([4],3:[2,1])) = snd (rev2(4:[],[3,2,1])) -> viens saraksts
snd (rev2([],4:[3,2,1])) = snd (rev2([],[4,3,2,1])) ->
snd ([],[4,3,2,1]) ->
[4,3,2,1]
Kārtošana: insert sort
Kārtošana, izmantojot ievietošanu (insertion sort)

insert x [] = [x]
insert x (h:t) = if x<h then x:(h:t) else h:(insert x t)

insert_sort [] = []
insert_sort (h:t) = insert h (insert_sort t)

Visa programma izveidota 4 rindiņās.


Vai C var uzrakstīt tik vienkāršu kārtošanas programmu?
Darbojas laikā, kas proporcionāls n2.
Kārtošana: merge sort
merge [] x = x
merge x [] = x
merge (a : al) (b : bl) =
if a < b then (a : (merge al (b : bl))) else (b : (merge (a:al) bl));
split [] = ([],[])
split (h:[]) = ([h],[])
split (h:(g:ls)) = let (a,b) = split ls in (h:a, g:b)
merge_sort [] = []
merge_sort [a] = [a]
merge_sort x =
let (e,f) = split x; a = merge_sort e; b = merge_sort f in (merge a b)
Šablonu atbilstība izmantota lokālajās deklarācijās – let izteiksmēs funkciju split un
merge_sort definīcijā.
Darbojas laikā n * log n
Testa piemēri: merge_sort (listgen 10000) insert_sort (listgen 10000)
next1 n = (n+17) `mod` 1000 -- Ievērojam apgrieztos apostrofus
listgen 0 = [0]
listgen n = let a = (listgen (n-1)) in (next1 (head a)):a
HASKELL: sarakstu apstrādes funkcijas
map - universāla (augstākas kārtas) funkcija, kas saņem kā pirmo argumentu funkciju
un pielieto to katram elementam sarakstā, kas saņemts kā otrais arguments
map1 f [] = []
map1 f (h:t) = (f h):(map1 f t)
map funkcija – iebūvēta Haskell standarta bibliotēkā (tādēļ šeit cits nosaukums).
Prelude> map (+ 2) [1 .. 5]
[3,4,5,6,7]
Prelude> map square [1 .. 5] Ja ir definīcija square x = x * x
[1,4,9,16,25]
Varēja rakstīt map (\x -> x*x) [1 .. 5]
Prelude> map fst [(1, 2), (3, 4), (5, 6)]
[1,3,5]
Prelude> filter (> 2) [1 .. 5]
[3,4,5] filter1 p [] = []
filter1 p (h:t) = if (p h) then h:(filter1 p t) else (filter1 p
t)
Virkne iebūvētu funkciju darbam ar sarakstiem, sk.
http://www.haskell.org/haskellwiki/How_to_work_on_lists
Fold (reduce) funkcijas
foldr f z [] =z
foldr f z (x:xs) = f x (foldr f z xs)
-- Aprēķina pirmo elementu, un tad pielieto funkciju f iegūtajam rezultātam, kā arī
rekursīvi no foldr izsaukuma iegūtajai vērtībai
foldl f z [] =z
foldl f z (x:xs) = foldl f (f z x) xs
-- Rekursīvi izsauc sevi, līdz saraksts ir tukšs, pēc tam veic aprēķinu
-- Atmiņas ziņā efektīvāks variants foldl’; https://wiki.haskell.org/Foldr_Foldl_Foldl%27
(«striktā aprēķināšana» foldl ietvaros)

> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b

> foldr (+) 0 [1..10] -- 0+1+2+3+4+5+6+7+8+9+10


55
> foldl (+) 0 [1..10]
55
Sarakstu definēšana; quicksort
Sarakstu definēšanas konstrukcijas:
square_list ls = [ x * x | x <- ls ],
atbilst map (\ x -> x * x) ls
square_list_pos ls = [ x * x | x <- ls, x > 0 ],
atbilst map (\ x -> x * x) ( filter (>0) ls)

Analoģiski veidam, kā matemātikā definē kopas.

Quicksort, saraksta kārtošana:


qsort [ ] = [ ]
qsort (h : t) = qsort [ x | x <- t, x <= h ] ++ [ h ] ++ qsort [ x | x <- t, x > h ]

Alternatīvs Quicksort, pievērst uzmanību atkāpēm!


qsort1 [ ] = [ ]
qsort1 (h : t) = smaller ++ [ h ] ++ larger
where smaller = qsort1[ x | x <- t, x <= h ]
larger = qsort1 [ x | x <- t, x > h ]
Haskell: ieskats tipu sistēmā
Pamata tipi: Bool, Char, Int, Integer, Float, Double, Rational, ...
Tipu konstrukcijas:
saraksti [False, True, False] :: [Bool]
viens noteikts tips, mainīgs garums
korteži (False,’a’,True) :: (Bool,Char,Bool)
noteikts garums, katrai komponentei savs tips
Funkciju tipi: a -> b
Vairākargumentu funkcijas:
add :: (Int, Int) -> Int
add(x,y)= x+y
plus :: Int -> Int -> Int Int -> (Int -> Int)
plus x y = x + y
> :t (plus 6)
(plus 6) :: Int -> Int
plus – var apstrādāt argumentus pēc kārtas.
Parastais veids funkciju definēšanā un izmantošanā
Funkciju tipi – tādi paši tipi, kā citi, var tikt izmantoti kortežos, sarakstos, utt....
Haskell: Tipu klases
Tipa piederība klasei nodrošina
noteiktu operāciju pieejamību.
Eq: ==, /= :: a -> a -> Bool
Show: show :: a -> String
Read: read :: String -> a
Ord: <, <=, >, >=
Num: +, -, *, ...
Enum: succ, pred, ...
HASKELL: tipu definēšana
Haskell:

- iebūvētie tipi pāru, kortežu, sarakstu uzdošanai,


type String = [Char]

- - iespējas definēt tipu sinonīmus (type) un


lietotāja definētus polimorfus tipus (data):

type ListFn a = [a] -> [a]


type Salary = Float
type Pair a b = (a,b)

data Color = Red | Green | Blue | Yellow;


data IntOrReal = IValue Integer | RValue Double;
data MyList = Empty | Next Int MyList;
data BTree = Leaf Integer | IVertex BTree Integer BTree;
data BST a = Nil | Node a (BST a) (BST a)

Tipu mainīgie – rakstīti aiz datu tipa vārda.

Tipu un konstruktoru vārdi – sākas ar lielajiem burtiem, funkciju un vērtību vārdi –


sākas ar mazo burtu.
HASKELL piemēri: lietotāja datu tipi
data BST a = Nil | Node a (BST a) (BST a) deriving (Show,Eq)

deriving (Show,Eq) jāraksta, lai šī tipa vērtības būtu iespējams izspīdināt uz ekrāna, kā
arī, lai tās būtu salīdzināmas, izmantojot vienādību.

Mehānisms – hierarhiski organizētas tipu klases, nosaka polimorfām operācijām


nosacījumus to pielietošanai.

Daži piemēri šajā datu tipā:

aaa = Nil

bbb = Node 4 Nil Nil

ccc = Node 4 (Node 3 Nil Nil) (Node 6 Nil (Node 7 Nil Nil))

Funkcija, kas izmanto šo datu tipu:

flatten:: BST a -> [a]


flatten Nil = []
flatten (Node val left right) =
(flatten left) ++ [val] ++ (flatten right)
IMP semantika valodā Haskell: izteiksmes (1)
data Aexp = Acon Integer | Var String | Plus Aexp Aexp |
Minus Aexp Aexp | Times Aexp Aexp
data Bexp = Bcon Bool | Eq Aexp Aexp | Ge Aexp Aexp |
Not Bexp | And Bexp Bexp | Or Bexp Bexp
Aritmētisko un loģisko izteiksmju semantika. Stāvoklis s – kodēts kā funkcija no
mainīgo vārdiem (tips String) uz mainīgo vērtībām.
Saglabāta tieša atbilstība ar teorētisko izteiksmju semantikas definējumu.
aval (Acon a) s = a
aval (Var a) s = s a
aval (Plus a b) s = (aval a s) + (aval b s)
aval (Minus a b) s = (aval a s) - (aval b s)
aval (Times a b) s = (aval a s) * (aval b s)
bval (Bcon x) s = x
bval (Eq a b) s = c where c = ((aval a s) == (aval b s))
bval (Ge a b) s = c where c = ((aval a s) >= (aval b s))
bval (Not b) s = not (bval b s)
bval (And b1 b2) s = bval b1 s && bval b2 s
bval (Or b1 b2) s = bval b1 s || bval b2 s
IMP semantika valodā Haskell: komandas (1)
data Com = Skip | Assign String Aexp | Seq Com Com |
If_c Bexp Com Com | While_c Bexp Com

Funkcija, kas maina mainīgā vērtību stāvoklī.


Piemērā tālāk pielietota 3 argumentiem (kā funkcijas rezultāts paliek funkcija).
do_assign x a s y = if x == y then (aval a s) else (s y)
Labāk:
do_assign x a s = let v = (aval a s) in \y -> if x==y then v else (s y)
:t do_assign
do_assign :: String -> Aexp -> (String -> Integer) -> String -> Integer

eval (Skip) s = s
eval (Assign x a) s = do_assign x a s
eval (Seq c1 c2) s = eval c2 (eval c1 s)
eval (If_c b c1 c2) s = if (bval b s) then (eval c1 s) else (eval c2 s)
eval (While_c b c) s = if (bval b s) then eval (While_c b c) (eval c s) else s

:t eval
eval :: Com -> (String -> Integer) -> String -> Integer

Kā nokodēt piemēros String -> Integer ? …


IMP semantika valodā Haskell: izteiksmes
data Aexp = Acon Integer | Var String | Plus Aexp Aexp |
Minus Aexp Aexp | Times Aexp Aexp
data Bexp = Bcon Bool | Eq Aexp Aexp | Ge Aexp Aexp |
Not Bexp | And Bexp Bexp | Or Bexp Bexp
Aritmētisko un loģisko izteiksmju semantika. Stāvoklis s – kodēts kā [(String,Integer)]
lookup1 _ [] = 0
lookup1 x ((n,v):t) = if x==n then v else (lookup1 x t)
aval (Acon a) s = a
aval (Var a) s = lookup1 a s
aval (Plus a b) s = (aval a s) + (aval b s)
aval (Minus a b) s = (aval a s) - (aval b s)
aval (Times a b) s = (aval a s) * (aval b s)
bval (Bcon x) s = x
bval (Eq a b) s = c where c = ((aval a s) == (aval b s))
bval (Ge a b) s = c where c = ((aval a s) >= (aval b s))
bval (Not b) s = not (bval b s)
bval (And b1 b2) s = bval b1 s && bval b2 s
bval (Or b1 b2) s = bval b1 s || bval b2 s
b = aval (Plus (Var "x") (Times (Var "y") (Acon 3))) [("x",14),("y",21)]  77
IMP semantika valodā Haskell: komandas
data Com = Skip | Assign String Aexp | Seq Com Com |
If_c Bexp Com Com | While_c Bexp Com

do_assign2 x a s = let v = (aval a s) in update x v s


update x v [] = [(x,v)]
update x v ((n,u):t) = if x==n then ((n,v):t) else ((n,u):(update x v t))

do_assign2 :: String -> Aexp -> [(String, Integer)] -> [(String, Integer)]

eval (Skip) s = s
eval (Assign x a) s = do_assign2 x a s
eval (Seq c1 c2) s = eval c2 (eval c1 s)
eval (If_c b c1 c2) s = if (bval b s) then (eval c1 s) else (eval c2 s)
eval (While_c b c) s = if (bval b s) then eval (While_c b c) (eval c s) else s

eval :: Com -> [(String, Integer)] -> [(String, Integer)]

gcd_program = While_c (Not (Eq (Var "x") (Var "y"))) c


where c = If_c (Ge (Var "x") (Var "y")) (Assign "x" (Minus (Var "x") (Var "y")))
(Assign "y" (Minus (Var "y") (Var "x")))

eval gcd_program [("x",14),("y",35)]  [("x",7),("y",7)]


Valodas IMP interpretators: stāvokļa «pārkodēšana»*
Ja nepieciešams strādāt ar stāvokļiem, kuri kodēti kā funkcijas (sākotnējais variants):
mk_state ((v,val):t) x = if v == x then val else (mk_state t x)
mk_state null x = 0
*Imp> :t mk_state
mk_state :: (Eq t, Num t1) => [(t, t1)] -> t -> t1
show_value s x = (x,s(x))
*Imp> :t show_value
show_value :: (t -> t1) -> t -> (t, t1)
show_state s l = map (show_value s) l
*Imp> :t show_state
show_state :: (a -> t) -> [a] -> [(a, t)]
run c s = show_state (eval c (mk_state s)) (map fst s)
*Imp> :t run
run :: Com -> [(String, Integer)] -> [(String, Integer)]
eval analogs, darbojas uz stāvokļiem kā pāru (mainīgais, vērtība) sarakstiem
HASKELL: datu ievads un izvads
Speciāla veida vērtības – notikumi (actions)
Izteiksmes, kuru vērtība ir notikums – komanda (command).
Komandai, kuras notikuma rezultāts ir ar tipu T, tips ir IO T .
Komandai, kuras notikumam nav rezultāta, tips ir IO () .
Piemēram: putStrLn :: String -> IO ()
getLine :: IO String
svx = putStrLn "Sveiks!" -- Komandas svx izsaukums izdrukās “Sveiks!”
dlg = do -- do – atļauj veidot virknes kompozīciju no komandām
putStrLn "Please enter your name: "
s <- getLine
putStrLn ("Hello, " ++ s ++ ", how are you?")
Darbības ar failiem: linetofile = do
s <- getLine
writeFile "a.txt" s
filetofile = do
s <- readFile "a.txt"
writeFile "b.txt" s
Tēma: aizturētā (“slinkā”)
programmu izpilde

Funkcionālā programmēšana
Aizturētā programmu izpilde
Aizturētā programmu izpilde: rēķināt argumenta vērtību funkcijai tikai tad, ja
arguments patiešām vajadzīgs.
Parametru nodošana apakšprogrammai pēc vārda (analoģiski ar ALGOL 60, šeit daudz
nopietnāki pielietojumi).
let val x = 3 * 2 in (4 * 2) - (x + x) end
Kādu aprēķināšanas secību izvēlēties?
a) aprēķināt x -> 6 un tad ievietot ?
b) ievietot x vietā izteiksmi 3*2 ? (jārēķina būs divreiz …)
c) ievietot norādi uz x, tad aprēķināt, ja vajadzīgs (otrajai reizei vērtība 6 jau būs
gatava) ?
Aizturētā izpilde: lietotājs var domāt, ka rīkojas atbilstoši (b), patiesībā implementācija
atbilst (c) . Memoizācijas semantika.
Argumentu var nevajadzēt (i), var būt tā, ka to arī nevar aprēķināt (ii):
(i) let val x = 3 * 2 in (4 * 2) - (5 + 7) end
(ii) let val x = 5 `div` 0 in (4 * 2) - (5 + 7) end
Aizturētā programmu izpilde (2)
Argumentu var nevajadzēt (i), var būt tā, ka to arī nevar aprēķināt (ii):
(i) let val x = 3 * 2 in (4 * 2) - (5 + 7) end
(ii) let val x = 5 div 0 in (4 * 2) - (5 + 7) end
Izteiksme ne-strikta, ja tai var būt definēta vērtība arī tad, ja argumenta vērtība ir
nedefinēta.
Striktas izteiksmes: GIGO princips (garbage in garbage out). Striktas izteiksmju
aprēķināšana ir vieglāk implementējama.
- termos: vai censties reducēt argumentu pirms ievietošanas ?
Teorēma par normālās formas unikalitāti: aprēķinu secības maiņa - rēķinos nevar
novest pie cita rezultāta (bet var novest pie situācijas, ka risinājums netiek atrasts).
Funkcionālā valodā šī teorēma spēkā, ja nav “blakus efektu” izteiksmēm.
Dažādi izpildes scenāriji funkcionālā valodā:
g ( f (x) ):
- aprēķināt f(x), tad virzīt to kā argumentu funkcijai g
- censties rēķināt g, rēķinot f tik daudz, lai g saņemtu tos datus, kas viņai vajadzīgi
… (var labi strādāt ar bezgalīgām datu struktūrām !)
Ģenerēšanas un filtrēšanas programmas
Iespējams valodā iekļaut potenciāli bezgalīgus objektus.
Plūsmas – saraksti ar slinko elementu aprēķināšanas secību.

Plūsmas viens no ģenerēšanas – filtrēšanas programmēšanas veidiem: ir


funkcijas, kas ģenerē plūsmas (ģeneratori), pēc tam citas funkcijas, kas šīs
plūsmas filtrē (filtri).

HASKELL ir līdzekļi ģeneratoru un filtru aprakstam:

square_list_pos ls = [ x * x | x <- ls, x > 0 ]


atbilst map (\ x -> x * x) ( filter (>0) ls)

Var darboties arī ar bezgalīgiem sarakstiem!

Tipiski piemēri, kur ģeneratoru / filtru programmēšana labi darbojas:


- noskaidrot, vai diviem kokiem sakrīt teksti, kas uzrakstīti lapās apgaitas
kārtībā.
- pirmskaitļu aprēķināšana, ar Eratostena sieta metodi
HASKELL: slinkā aprēķināšana
fx=2 - argumenta x vērtība nekad netiks aprēķināta
my_if x y z = if x then y else z – uzvedīsies tāpat, kā iebūvētais if operators
Slinkā aprēķināšana: saraksti ir ekvivalenti ar plūsmām, varam definēt:
ones = 1 : ones - bezgalīgs saraksts ar vieniniekiem.
Saīsinājumi: [ n .. ] – veselie skaitļi, sākot no n, [1,1 .. ] – saraksts no visiem
vieniniekiem, [2,4 .. ] – saraksts no visiem pāra skaitļiem, sākot ar 2
fives = 5 : fives -- dažādu definīciju eksperimenti
natnums = [ 1 .. ]
from13up = [13 .. ]
evenFrom10 = [10,12 ..]
Funkcijas daļējai bezgalīga saraksta aprēķināšanai: take – paņem pirmos n elementus
no saraksta, drop – atmet pirmos n elementus no saraksta
take 0 _ = [ ] drop 0 ls = ls
take _ [ ] = [ ] drop _ [ ] = [ ]
take n (h:t) = h : take (n-1) t drop n (_:t) = drop (n-1) t
take 5 ones dod [1,1,1,1,1]
take 5 (drop 4 [2 .. ]) dod [6,7,8,9,10]
HASKELL: slinkā aprēķināšana
ones = 1 : ones

initpairlist n = take n (zip [ 1 .. ] [ 1 .. ] )


initsqlist n = take n (zipWith (*) [ 1 .. ] [ 1 .. ] )

Fibonači skaitļi
fib = 0 : 1 : [ a + b | (a,b) <- zip fib (tail fib) ]
fibsn n = take n fib

Alternatīva definīcija:
fibslist1 n = take n fibs1 where fibs1 = 0:1:zipWith (+) fibs1 (tail fibs1)

Eratostena siets:
sieve (p : lis) = p : sieve [ n | n <- lis, mod n p /= 0]
primes = sieve [ 2 .. ]
> take 20 primes
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71]
Slinkā programmu izpilde: piemēri
Kvadrātsaknes aprēķināšana, Ņūtona - Rapsona algoritms, N:
Iterāciju virkne: a0, a1, …, an, … an+1 = (an + N/ an) / 2
Realizācija:
Definējam nākamo iterāciju:
next n x = (x + n/x)/2
Bezgalīga iterāciju struktūra (ģenerators):
repeat1 f a = a : (repeat1 f (f a))
repeat ( next n ) a0 rezultāts: [a0,a1,a2,…] - bezgalīgs saraksts!
Nosacījums, cik daudz datus no bezgalīgās struktūras ņemsim (filtrs):
within eps (a:b:rest) = if abs(a-b) <= eps then b
else within eps (b:rest)
sqrt a0 eps n = within eps (repeat1 (next n) a0)
Cita veida nosacījums uz šo pašu bezgalīgo virkni (cits filtrs):
relative eps (a:b:rest) = if abs(a/b-1) <= eps then b
else relative eps (b:rest)
sqrt1 a0 eps n = relative eps (repeat1 (next n) a0)
Tēma: Monādes valodā Haskell

Funkcionālā programmēšana
Monādes, aprēķini ar «kontekstu»
Pievieno «čaulu» apkārt vērtībai.
Piemēra «čaula»: pievienojam pāra otro elementu. 3 -> (3,0)
Monāde: jauna Ieguldījums
vērtība kontekstā
1) Funkcija return lai iegūtu sākotnējo monādisko vērtību: 3 -> (3,0)
Piemērs: return x = (x,0)
2) Funkcija >>= monādisko vērtību kompozīcijai (kombinē (x,n) ar \x -> (y,k) lai
iegūtu (y, update_context k n))
Piemērs: (>>=) (x,n) f = let (y,k) = f x in (y, n+k) update_context ir +
Rezultāts atkal ir Konkrētā monāde
Write (x,n) >>= \x -> (y,k) definē, kā konteksts
(vērtība, konteksts)
vērtība ar tiek ietekmēts
kontekstu kompozīcija Attēlo vērtību par
jauno vērtību un
f x = (x+1,2) ieguldījumu kontekstā
Citas definīcijas:
g x = (x*2,3) return x = (x,1)
Tad g.f.f.return 5 -> g.f.f (5,0) -> g.f (6,2) -> g(7,4) -> (14,7) (x,n) >>=
\x -> (y,k) = (y, n*k)
Rakstām (return 5) >>= f >>= f >>= g Rezultāts -?
Tā pati programma pār
Vērtību konteksts (otrā komponente) nav redzama programmā citu monādi.
Monādes piemērs: Maybe
data Maybe a = Nothing | Just a (Maybe datu tips)
halve :: Int -> Maybe Int
halve (even x) = Just (x `div` 2)
halve (odd x) = Nothing
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
Maybe kā monāde:
https://riptutorial.com/haskell/example/6144/the-maybe-monad
instance Monad Maybe where
return = Just x
fail = Nothing
Nothing >>= f = Nothing
return :: a -> Maybe a
(Just x) >>= f = f x return x = Just x
Maybe x: aprēķini, kam var būt (>>=) :: Maybe a -> (a->Maybe b)-> Maybe b
un var nebūt rezultāts (>>=) m g = case m of Nothing -> Nothing
Just x -> g x
«Iespējamības» čaula ap vērtībām.
Vērtības neesamība (Nothing) apstrādāta nevis kodā, bet monādē.
HASKELL: datu ievads un izvads
Speciāla veida vērtības – notikumi (actions)
Izteiksmes, kuru vērtība ir notikums – komanda (command).
Komandai, kuras notikuma rezultāts ir ar tipu T, tips ir IO T .
Komandai, kuras notikumam nav rezultāta, tips ir IO () .
Piemēram: putStrLn :: String -> IO ()
getLine :: IO String
svx = putStrLn "Sveiks!" -- Komandas svx izsaukums izdrukās “Sveiks!”
dlg = do -- do – atļauj veidot virknes kompozīciju no komandām
putStrLn "Please enter your name: "
s <- getLine
putStrLn ("Hello, " ++ s ++ ", how are you?")
Darbības ar failiem: linetofile = do
s <- getLine
writeFile "a.txt" s
filetofile = do
s <- readFile "a.txt"
writeFile "b.txt" s
Monādu klase, IO monāde
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b («then» operācija, kombinē monādisko
vērtību ar tipu m a un funkciju ar monādisko tipu a -> m b)
(>>) :: m a -> m b -> m b (2 monādisko vērtību kombinācija)
return :: a -> m a (no «tīrās» vērtības iegūt atbilstošo monādisko)
fail :: String -> m a (kļūdas ziņojuma funkcija)

Piemēram: putStrLn :: String -> IO ()


getLine :: IO String
Monādes tips m – parametrizēts tips (a, b – tipa parametri)
Monāde: «čaula» apkārt vērtībām.
Var uzskatīt, ka I/O operācijās «čaula» ir «reālās pasaules» transformācijas:
(RealWorld ir skaidrojuma labad ieviests datu tips)
type IO = RealWorld -> RealWorld
type IO a = RealWorld -> (a,RealWorld)
putStrLn :: String -> IO () String -> RealWorld -> ((),RealWorld)
getLine :: IO String RealWorld -> (String,RealWorld)
>>= - veids, kā kombinēt virknē vairākas IO monādei atbilstošas darbības
Monādu klase, IO monāde
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b («then» operācija, kombinē monādisko
vērtību ar tipu m a un funkciju ar monādisko tipu a -> m b)
(>>) :: m a -> m b -> m b (2 monādisko vērtību kombinācija)
return :: a -> m a (no «tīrās» vērtības iegūt atbilstošo monādisko)
fail :: String -> m a (kļūdas ziņojuma funkcija)
putStrLn :: String -> IO () String -> RealWorld -> ((),RealWorld)
getLine :: IO String RealWorld -> (String, RealWorld)
dlg1 =
putStrLn "Please enter your name: " >>
getLine >>=
\s -> putStrLn ("Hello, " ++ s ++ ", how are you?")
putStrLn: String -> RealWorld -> ((),RealWorld)
\s -> putStrLn ("Hello, " ++ s ++ ", how are you?"): String -> RealWorld -> ((),RealWorld)
getLine >>=
\s -> putStrLn ("Hello, " ++ s ++ ", how are you?"): RealWorld -> ((),RealWorld)
getLine: RealWorld -> (String, RealWorld)
>>= : (RealWorld -> (String, RealWorld)) -> (String -> RealWorld -> ((),RealWorld))
-> RealWorld -> ((),RealWorld)
>>=: IO String -> (String -> IO () ) -> IO ()
Monādu klase, IO monāde
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b («then» operācija, kombinē monādisko
vērtību ar tipu m a un funkciju ar monādisko tipu a -> m b)
(>>) :: m a -> m b -> m b (2 monādisko vērtību kombinācija)
return :: a -> m a (no «tīrās» vērtības iegūt atbilstošo monādisko)
fail :: String -> m a (kļūdas ziņojuma funkcija)
I/O realizācija atbilst IO monādei. IO monāde – tips, kas ir monādu klases instance, realizē
monādu operācijas.
putStrLn :: String -> IO () String -> RealWorld -> ((),RealWorld)
getLine :: IO String RealWorld -> (String, RealWorld)

dlg = do -- do – atļauj veidot virknes kompozīciju no komandām


putStrLn "Please enter your name: "
s <- getLine
putStrLn ("Hello, " ++ s ++ ", how are you?")
dlg1 = -- dlg pieraksts ir saīsinājums no dlg1.
putStrLn "Please enter your name: " >>
getLine >>=
\s -> putStrLn ("Hello, " ++ s ++ ", how are you?")
Likumi: do e1 ; e2 = e1 >> e2
do p <- e1; e2 = e1 >>= \p -> e2
Stāvokļu monāde: funkcionalitāte
type StateTrans s a = s -> (s, a) -- Transformē stāvokli par stāvokli un izdod rezultātu
(>>=) :: StateTrans s a -> (a -> StateTrans s b) -> StateTrans s b
p >>= k = -- p: StateTrans s a, k: a -> StateTrans s b
\s0 -> let (s1, a) = p s0; q = k a in q s1
return :: a -> StateTrans s a return a = \s -> (s, a)
Piemērs bez monādēm:
f1 w a = let (b, x) = g1 w a a – sākuma stāvoklis, d – beigu
(c, y) = h1 w x b stāvoklis,
(d, z) = i1 w x y c in (d, z) b,c – starpstāvokļi
g1 w, h1 w b, i1 w x y - izrēķina
Variants ar monādēm:
vērtību un maina «stāvokli»
fw= g w >>= Analogs C kods:
\x -> h w x >>= int f1(float w) {
\y -> i w x y >>= const char x = g1(w) ;
\z -> return z const double y = h1(w, x) ;
f w = do x <- g w const int z = i1(w, x, y) ;
y <- h w x return z ; }
z <- i w x y (g1, h1, i1 var atsaukties uz
return z stāvokli)

Avots: http://www.engr.mun.ca/~theo/Misc/haskell_and_monads.htm

You might also like