FUP Flashcards
(55 cards)
Funkcionalni programovani
programovaci styl zalozenny na strukturaci programu jako kompozici cistych funkci
Cista funkce (pure)
takova funkce, ktera pri stejnem inputu VZDY vrati stejny output
Nema vedlejsi ucinky - nemodifikuje zadna data, nema vliv na okolni svet a struktury
- je nezavisla na vnejsim svetu, pouze sama se sebou pracuje
cisty program - sestaven pouze z cistych funkci
Vyhody cistych funkc
nezavisle na okoli - udrzitelne
- jednoduche testovani
- znovupouzitelne nezavisle na kontextu
- rozsiritelne
- paralelni
Dusledky cistych funkci/funckionalniho programovani - hlavni vlastnosti
Rekurze oproti smycka
- smycka meni stav, rekurze ma stav jako stack
- immutable datove struktury
- zmena stavu datove struktury se provadi jeji kopii a zmenou
- perzistentni datove struktury pro mensi pocet kopii datovych struktur
- takova struktura, ktera vytvori pouze cast kopie, ktera je ovlvienan zmenou, zbytek nedotcene struktury se pouze odkazuje na puvodni strukturu
- tedy vytvori se cast nove struktury a nedotcena cast ukazuje na puvodni jeste
Jde mit plne pure program?
Ne, potrebujeme alespon nejakou zmenu stavu (treba vypis do konzole)
proto zadny program neni 100% pure, ale mutable cast kodu, ktera zavisi na nejakem stavu se snazime minimalizovat co nejvic
Lisp, Scheme, Racket, DrRacket
Lisp - list processor (jazyk, ve kterem vsechno je list), prefixova notace
Scheme - dialect of Lisp
Racket - another dialect
DrRacket - IDE pro Racket
Racket semantika/co je psany program
Program v raketu - vyraz, ktery je postupne vypocitavan/evaluovan od nejvnitrnejsiho po vnejsi dokud nedojdeme k posledni hodnote
Stretegie vyhodnocovani vyrazu
Strategie, kdy se hodnoty vyrazu spocitaji, zda hned, nebo na konci, nebo postupne atd…
Hlavne dulezite v parametrech funkci a podminkach:
treba jak se vyhodnoti volani funkce s parametry:
(define (square x) (* x x)) // definuju funkci square
(square (+3 4)) => (square 7) => (* 7 7) => 49
- Striktni strategie Racketu - VSECHNY parametry se nejdriv vyhodnoti zleva doprava, az pote se vola funkce
(square (+3 4)) => (* (+ 3 4) (+ 3 4)) => (* 7 7) => 49
- lazy evaluace - nejdriv se aplikuje funkce, a az pote se dopocitaji vnitrni parametry - pripad podminek
Racket funkce - eager
Racket podminky - lazy
Syntax podminek racketu
(if test-exp then-exp else-exp)
(if (> 0 1) 1 2) => 2
(if (< 0 1) 1 (+ 3 “a”)) => 1
(cond [test-exp1 exp]
[test-exp2 exp]
…
[else exp])
(cond [(odd? 12) 1]
[(even? 12) 2]
[else 3]) => 2
Zakladni datove typy Racketu
Numbers - +, -, /, abs, sqrt, number?, <, >, =…
Bool - #t, #f, and, or, not, boolean?
String - “abc”, string?, substring, string-append
Characters - #\A, #\@, char?m char->integer, integer->char, list->string
Typy rekurzi
Linearni - jedno rekurzivni volani v tele
Stromova - vice rekurzivnich volani v tele
- podtyp stromove - TAIL - vysledek rekurze je vysledek i funkce
Priklad TAIL rekurze
Klasicke vyuziti kdyz v rekurzi agregujeme nejakou hodnotu
Typicky priklad
podminka?
- pokud ne -> vrat basic pripad
- pokud ano -> aktualni_hodnota * rekurze(mensi hodnota)
(define (fact n)
(if (<= n 1)
1
(* n (fact (- n 1)))))
Bez tail rekurze ma to extremne moc volani v pameti protoze se zavola:
(fact 4) => (* 4 (fact 3))
=> (* 4 (* 3 (fact 2)))
=> (* 4 (* 3 (* 2 (fact 1))))
=> (* 4 (* 3 (* 2 (* 1 1)))) => 24
Tedy narocnost pameti je O(n) -> tolik volani, jake je cislo ale v kazde hladine se zvetsuje do sirky
Kdybych vysledek postupne agregoval v argumetnu predavanem:
(define (fact n [acc 1])
(if (<= n 1)
acc
(fact (- n 1) (* n acc))))
Tak postupne mi narusta hodnota mezivysledku faktorialu:
(fact 4) = (fact 4 1)
=> (fact 3 4)
=> (fact 2 12)
=> (fact 1 24)
=> 24
a mam pouze jednu sirku v patre, tedy O(1) pametova narocnost
Jak vytvorit par, list v Racketu, car, cdr, …
Par - umoznuje konstrukci hierarchickch datovych struktur
- treba i ruzne typy
(cons 1 2) => ‘(1 . 2)
(car (cons 1 2)) => 1
(cdr (cons 1 2)) => 2
(cons (cons 1 (cons 2 3)) 4) // si nemusim predstavovat jako linearni pole paru, ale hierarchickou strukturu jako (cons X 4), kde X v sobe obsahuje dalsi jemenjsi deleni do stromu paru
List - sekvence paru s prazdnym listem na konci:
- je to jako linked list, kde je to lienarni struktura a kazdy prvkek odkazuje na dalsi, na konci je prazdny ‘()
(cons 1 (cons 2 (cons 3 (cons 4 ‘()))))
(list 1 2 3 4)
(car (list 1 2 3)) => 1 ; also: first
(cdr (list 1 2 3)) => (2 3) ; also: rest
(caddr (list 1 2 3)) => 3
(list? (list 1 2 3)) => #t
(empty? (list 1 2 3)) => #f
(list 1 2 (+ 1 2)) => ‘(1 2 3)
(append ‘(1) ‘(2) ‘(3 4)) => ‘(1 2 3 4)
Druhy porovnani v Racket
= pro numerickou rovnosti
eq? pro ukazatelovou rovnost
equal? pro strukturalni rekurzivni rovnost, pro slozitejsi pole a struktury
lambda
anonymni funkce, kterou nemusime pojmenovavat, jen uvedeme funckionalitu
pouziva se jako “inline” definice funkce v ramci treba mapu, nebo filtru:
(filter (lambda (x) (< x 5))
‘(1 7 3 8)) => ‘(7 8)
Another useful function iterating through a list is map.
(map f lst) applies f to each element of lst:
(map (lambda (x) (* x x)) ‘(1 2 3)) => ‘(1 4 9)
map f lst -> namapuje/provede funkci po elementech na kazdy posle
Operator let* v Racket
Umoznuje zadefinovat lokalni promenna/data pro prehlednejsi kod
drzi mezivypocty treba abych nehromadil milion zavorek
Stromy v Racket
Reprezentovany jako vnorene listy ‘(data left right)
‘(1 (2 (4 (7 #f #f) #f)
(5 #f #f))
(3 (6 (8 #f #f)
(9 #f #f))
#f))
Tedy 1-2,3
2-4,5
7 - f, f
3-6,f
8-f,f
9-f,f
Rekurze stromem v Racketu
(define get-data car) //prvni prvek listu - koren
(define get-left cadr) // druhy prvek listu - levy potomek
(define get-right caddr) // druhy prvek listu - pravy
(define (find pred tree)
(if tree
(let* ([data (get-data tree)]
[left (find pred (get-left tree))] // rekurze stromem vlevlo
[right (find pred (get-right tree))] // rekurze stromem vpravo
[both (append left right)]) // vysledek obou rekurzi
(if (pred data)
(cons data both) //sluc vysledek treba…
both))
‘()))
Evaluace vyrazu treba kod
Rekurzivne rozbijim vyraz na mensi celky, kdyz jsem narazil na zakladni prvek -> vrat ho
Dokud mam co evaluovat -> rekurzivne evaluuj po komponentach:
Nejdriv evaluuju vsechny arguemtny, pak podle operace na ne ji aplikuju:
(define (eval-expr e)
(if (number? e)
e
(let ([op (car e)]
[children (map eval-expr (cdr e))])
(cond
[(eq? op ‘+) (apply + children)]
[(eq? op ‘-) (apply - children)]
[(eq? op ‘*) (apply * children)]
[(eq? op ‘opp) (- (car children))]))))
apply takes a function and a list of arguments, “unwraps” the
arguments - (apply + ‘(1 2 3)) => (+ 1 2 3)
High-order funkce, vyssi funcke
Takove funkce, ktere:
1. Jako argument prijimaji jine funkce
2. Vraci funkci jako navratovou hodnotu
3. Oboje naraz
Slouzi k vyssi urovne abstrakce, znovupouzitelnost navrhu programu (nemusim pokazde prepisovat funkci, staci do ni pouze vlozit jinou funkci, treba
apply f args -> nezavisle na f, tedy to je znovupouzitelny kus kodu
Priklady funkci vyssiho radu
- Filter - filter elements of a list by some predicate
(filter pred lst) -> (lst) // without elements that does not satisfy pred - Map - applies func to each element of a list
(map f lst) -> ‘(f(l1) f(l2) … f(ln)) - Apply - “inserts” function between each element of a list (reduction)
(apply f lst) -> (l1 f l2 f l3 f … ln) - fold (l/r) - aggregates elements in a list by binary operation from left/right
(foldr + 0 (1 2 3)) -> (((0+1) + 2) + 3) = 6, postupna redukce, agregace dat podle funkce f od pocatecni hodnoty neutralni treba
Castecna evaluace, curry
Princip curry v programovacím jazyce jako je Racket se týká transformace funkce s více argumenty na posloupnost funkcí s jedním argumentem. V podstatě to znamená, že namísto toho, aby byla funkce aplikována na všechny argumenty najednou, je možné postupně aplikovat na každý argument zvlášť.
Klasicka funcke bez curry:
(define (add a b)
(+ a b))
S curry
(define (curry-add a)
(lambda (x)
(+ a x)))
- tedy nesectu parametry hned, ale pouze vratim jinou funkci (ktera uz ma prednastaveny jeden arguemnt) a pouze ceka na dalsi vstup.
Je to vyhodne prednastaveni parametru, ktere se nebudou menit a na ne mohu nabalovat dalsi parametry ktere uz jsou prommenne.
Vyhodou je postupne nabalovani argumentu do funkce jako pipelining, aniz bych musel porad voalt tu samou funkci dokola, ale menim jen jeden argument.
Je to optimalni zrychleni, kdy funkci predpripavi predem.
Pouziti dobre treba v map, kdy na kazdy element pole poslu curry funkci:
(define (add a b) (+ a b))
(define add5 (curry add 5)) ; add5 je funkce (lambda (x) (+ 5 x)), tedy vytvoril jsem jakousi “makro-rutinu”, ktera pricte 5 k jakemukoliv arguemtnu, ale uz ji nemusim volat pokazde se dvema argumentami
(map add5 ‘(1 2 3)) ; Výstup: ‘(6 7 8)
Dalsi vyhodou je vytvoreni jakehosi pomocneho makra pro lepsi vizualizaci a citelnost kodu:
(define add5 (curry add 5)) ; vytvoříš si pomocníka/makro
tedy toto makro muzu poslat na jina cisla
(add5 3) ; můžeš používat dál -> 8
Curry vs lambda funkce
Curry mi muze jednoduse a PREHLEDNE nahradit zbytecne hromadeni kodu pri psani lambda funkci ve funkcich vyssiho radu:
treba chci kazdy prvek v poli vynasobit dvema:
(1 2 3) -> (2 4 6)
Pristup pomoci lambda funcke:
(map (lambda (x) (* 2 x)) (1 2 3)) -> tedy jsem zbytecne zabral misto a zneprehlednil kod…
Pristup pomoci curry:
(map (curry * 2) (1 2 3)) -> prehlednejsi postupne aplikovani *2 na kazdy prvek
klicove slovo curry transofrmuje funkci v jeji curry verzi:
treba (+ 2 3) -> ((curry + 2) 3)
Skladani funkci f,g
Klasicky jako v linearni algebre:
g pote co f:
gof = g(f(x)) a muzu nabalovat dalsi a dalsi funkce…
Vzdy vyhodnotim vnitek, ten se stane argumentem pro vnejsi atd…
Bud hromadim zavorky nebo pouziju klicove slovo compose