-- Haskell. Cursul 5 -- Tudor Berariu, 2018 -- ** Lungimea unei liste ** {- Vom scrie diferite definiții pentru o funcție ce calculează lungimea unei liste indiferent de tipul elementelor ei. -} {- Cel mai simplu mod de defini funcția este folosind mecanismul `pattern matching`. -} length01 [] = 0 length01 (x : xs) = 1 + length01 xs {- Funcția `length01` folosește operatorul `+`. Îl putem folosi ca funcție? Desigur. -} length02 [] = 0 length02 (x : xs) = (+) 1 (length02 xs) {- Funcția `length02` are nevoie de paranteze în jurul aplicației recursive. O variantă este să folosim `$`. -} length03 [] = 0 length03 (x : xs) = (+) 1 $ length03 xs {- Toate cele trei funcții scrise anterior leagă variabila `x` la primul element din listă, dar nu o utilizează. Putem semnaliza faptul că nu ne interesează valoarea pe care o are primul element din listă folosind variabila anonimă `_`. -} length04 [] = 0 length04 (_ : xs) = (+) 1 $ length04 xs {- Tipul funcției `length04` este Num s => [t] -> s. Ce se întâmplă dacă folosim `succ` în locul adunării cu 1? -} length05 [] = 0 length05 (_ : xs) = succ $ length05 xs {- Tipul funcției `length05` este (Num s, Enum s) => [t] -> s. Folosirea lui `succ` introduce o nouă "constrângere" asupra tipului valorilor întoarse de funcție. În locul aplicării succesive a funcțiilor `length05` și `succ` putem compune cele două funcții și aplica apoi rezultatul asupra lui xs. -} length06 [] = 0 length06 (_ : xs) = (succ . length06) xs {- Tipul funcției `length06` este (Num s, Enum s) => [t] -> s. Putem forța un tip anume al valorilor întoarse indicând explicit tipul funcției. -} length07 :: [a] -> Int length07 [] = 0 length07 (_ : xs) = (succ . length07) xs {- În funcția `length07` am aplicat compunerea folosind `succ`. Am putea folosi aceeași idee pentru operatorul `+`? -} length08 [] = 0 length08 (_ : xs) = ((+ 1) . length08) xs {- Putem schimba și ordinea regulilor atât timp cât nu este afectată logica funcției. -} length09 (_ : xs) = (( + 1) . length09) xs length09 _ = 0 {- Toate definițiile de mai sus (`length01`, ... , `length09`) s-au folosit de structura argumentului primit pentru a alege expresia la care să se evalueze aplicația funcției. Putem folosi `if` pentru asta? -} length10 l = if l == [] then 0 else 1 + length10 (tail l) {- La fel ca mai devreme putem folosi `$` pentru a ghida evaluarea expresiei în locul parantezelor. -} length11 l = if l == [] then 0 else (+ 1) $ length11 $ tail l {- O variantă la aplicarea succesivă a funcțiilor `tail`, `length` și `succ` este aplicarea compunerii lor asupra lui `l`. -} length12 l = if l == [] then 0 else ((+ 1) . length12 . tail) l {- Tipul lui `length12` este (Num c, Eq t) => [t] -> c. Folosirea operatorului `(==)` cere ca tipul elementelor din listă să poată fi comparat, deși acest lucru este nenecesar. Cum rolul acelei verificări este de a vedea dacă lista este vidă, putem rescrie utilizând funcția `null`. -} length13 l = if null l then 0 else ((+ 1) . length13 . tail) l {- Tipul lui `length12` este (Num c) => [a] -> c. Observați că dispare constrângerea de a putea verifica egalitatea dintre valorile tipului elementelor listei. -} {- Dacă mecanismul bazat pe `pattern matching` privește structura argumentelor, gărzile pot fi folosite pentru verificări generice asupra acestora. -} length14 l | null l = 0 | otherwise = ((+ 1) . length14 . tail) l {- `where` permite legarea statică a variabilelor la nivelul funcției. -} length15 l | null l = 0 | otherwise = ((+ 1) . length15) t where t = tail l length16 l = if null l then 0 else ((+ 1) . length16) t where t = tail l {- Legarea lui `t` la `tail l` înainte de a verifica dacă lista `l` are elemente este posibilă datorită evaluării leneșe din Haskell. `where` permite, de asemenea, descompunerea argumentelor. -} length17 l | null l = 0 | otherwise = ((+ 1) . length17) t where (_:t) = l length18 l = if null l then 0 else ((+ 1) . length18) t where (_:t) = l {- `where` folosit în funcțiile `length16` și `length18` poate fi înlocuit cu un `let` care de asemenea leagă static variabile locale. -} length19 l = let t = tail l in if null l then 0 else ((+ 1) . length19) t length20 l = let (_:t) = l in if null l then 0 else ((+ 1) . length20) t {- Spre deosebire de `where`, `let` poate fi folosit la nivelul oricărei expresii. -} length21 l = if null l then 0 else let t = tail l in ((+ 1) . length21) t {- `let` permite legarea mai multor variabile, indiferent de ordinea acestora -} length22 l = if null l then 0 else let t = tail l; lt = length22 t in (+ 1) lt length23 l = if null l then 0 else let len = (+ 1) lt; lt = length23 t; t = tail l in (+ 1) lt {- Funcția `length23` nu este un exemplu de programare frumoasă. NU faceți așa ceva acasă! Se poate utiliza și case .. of. -} length24 l = case l of [] -> 0 otherwise -> ((+ 1) . length24 . tail) l {- Și în acest caz putem folosi pattern matching. -} length25 l = case l of [] -> 0 (_:xs) -> ((+ 1) . length25) xs {- Toate definițiile anterioare sunt recursive. Putem folosi funcționale în locul recursivității explicite? -} length26 l = foldl (\acc _ -> 1 + acc) 0 l length27 l = foldr (\_ acc -> 1 + acc) 0 l {- Cum toate funcțiile în Haskell sunt curry, putem lega `length` direct la aplicarea parțială a foldului. -} length28 = foldl (\acc _ -> 1 + acc) 0 length29 = foldr (\_ acc -> 1 + acc) 0 {- și doar pentru a avea fix treizeci... -} length30 = sum . (map (\_ -> 1)) {- Verificare -} funcs = [length01, length02, length03, length04, length05, length06, length07, length08, length09, length11, length10, length12, length13, length14, length15, length16, length17, length18, length19, length20, length21, length22, length23, length24, length25, length26, length27, length28, length29, length30] lists = [[1 .. x] | x <- [0, 2 .. 100]] ok = and $ map (\f -> and (map (\l -> length l == f l) lists)) funcs