Programowanie Funkcyjne Marcin Kubica Świder, 28-04-2015
Czym jest programowanie funkcyjne? Obliczalne pojęcia matematyczne. Definicje stałych i funkcji i relacji. Wszystkie definicje są konstruktywne, tzn. określają sposób obliczenia danej wartości. Czy to wystarcza, żeby programować? TAK!
Czym nie jest programowanie funkcyjne? Brak zmiennych i przypisania. Brak pętli i instrukcji. Wzory matematyczne, to nie ciąg czynności do wykonania. We wzorach matematycznych brak jest pojęcia czasu.
Prosty przykład Dane są dwa prostokąty o bokach równoległych do osi układu współrzędnych. Jaka jest powierzchnia ich przecięcia? (x' 2,y' 2 ) (x 2,y 2 ) (x 1,y 1 ) (x' 1,y' 1 )
Prosty przykład c.d. Prostokąt można potraktować jak produkt kartezjański dwóch przedziałów. Problem można sprowadzić do przecięcia dwóch przedziałów. Powierzchnia przecięcia ((x 1,y 1 ),(x 2,y 2 )) i ((x' 1,y' 1 ),(x' 2,y' 2 )) = min (x 2,x' 2 ) max (x 1,x' 1 ) * min (y 2,y' 2 ) max (y 1,y' 1 )
Prosty przykład c.d. Taki wzór to już prawie program: let przeciecie (x1,x2) (y1,y2) = max (min x2 y2 - max x1 y1) 0;; let powierzchnia ((x1,y1),(x2,y2)) ((x1p,y1p),(x2p,y2p)) = przeciecie (x1,x2) (x1p,x2p) * przeciecie (y1,y2) (y1p,y2p);;
Silnia przykład rekurencji Wzór matematyczny na silnię: 1 jeśli n 1 n!={ } n (n 1)! wpp. Program: let rec fac n if n <= 1 then 1 else n * fac (n 1);; Obliczenie: fac 4 = 4*fac 3 = 4*3*fac 2 = 4*3*2*fac 1 = 4*3*2*1 = 24
Liczby Fibonacciego pierwsza odsłona Definicja ciągu Fibonacciego Fib 1 =Fib 2 =1 Fib n +2 =Fib n + Fib n+1 let rec fib n = if n <= 2 then 1 else fib (n - 1) + fib (n 2);; fib 5 = fib 4 + fib 3 = fib3 + fib 2 + fib 2 + fib 1= fib 2 + fib 1 + 1 + 1 + 1 = 1 + 1 + 1 + 1 + 1 = 5
Liczby Fibonacciego druga odsłona Zdefiniujmy pomocniczą funkcję f : f ( Fib k, Fib k+1,n)=fib k +n let rec f(a, b, n) = if n = 0 then a else f(b, (a + b), (n-1));; let fib n = f(1, 1, n-1);; fib 5 = f(1, 1, 4) = f(1, 2, 3) = f(2, 3, 2) = f(3, 5, 1) = f(5,8,0) = 5
Liczby Fibonacciego, c.d. W programowaniu funkcyjnym często łatwo napisać (nieefektywny) program. Napisanie programu efektywnego wymaga więcej wysiłku. Ćwiczenie: Jak zapisać jeszcze efektywniejszy program obliczający liczby Fibonacciego?
Garść technikaliów Podstawowe typy wartości: liczby całkowite (42, 2+2), liczby zmiennopozycyjne (3.14, 8.0 *. 5.25), wartości logiczne (true, flase, &&,, not), ciągi (listy) ([1; 2; 3], [], głowa::ogon), pary, trójki, n-tki ( (4, true, 5.34), (x, y), () ).
Garść technikaliów, c.d. Dopasowywanie wzorców: match <wartosc> with <wzorzec> -> <wartosc>... <wzorzec> -> <wartosc> match [1;2;3] with [] -> 0 h::t -> 2 * h match (3,6) with (x,y) -> x*y + (x+1)*y
Konstytucja programowania funkcyjnego Funkcje są pełnoprawnymi obywatelami, tak jak inne wartości. Konsekwencje: definiowanie (nazwanych) funkcji i stałych, funkcje mogą być wartością wyrażeń, funkcje mogą być argumentami funkcji, funkcje mogą być wynikami funkcji.
λ-abstrakcja Forma zapisu funkcji nienazwanych. Co jest potrzebne do zdefiniowania funkcji? x 2 x+1 fun x -> 2 * x + 1 Poniższe definicje są równoważne: let kwadrat x = x * x;; let kwadrat = fun x -> x * x;;
Funkcje wyższych rzędów Składanie funkcji let zloz f g = fun x -> f (g x);; Identyczność let id x = x;; Potęgowanie funkcji let rec potega f n = if n = 0 then id else zloz (potega f (n-1)) f;; Jaka jest dziedzina i przeciwdziedzina funkcji potega?
Funkcje przetwarzające listy Przykłady typowych funkcji przetwarzających listy: let rec length l = match l with [] -> 0 h::t -> 1 + length t;; let rec sumuj l = match l with [] -> 0 h::t -> h + sumuj t;;
Funkcje przetwarzające listy, c.d. Ogólny schemat funkcji przetwarzającej listy, jako funkcja wyższego rzędu: let rec fold f a l = match l with [] -> a h::t -> fold f (f a h) t;; Zastosowanie schematu: let length l = fold (fun a x -> a + 1) 0 l;; let sumuj l = fold (+) 0 l;;
Origami przykład zastosowania funkcji wyższych rzędów Zadanie: Dana jest kartka papieru oraz ciąg złożeń. Zaprogramuj funkcję, która dla danego punktu określa ile warstw papieru jest w danym punkcie. Na początku kartka to kwadrat jednostkowy. Zgięcia są dane jako proste (skierowane), wzdłuż których zginamy papier (z lewa na prawo). Stan kartki można reprezentować za pomocą funkcji, która punktom przyporządkowuje liczbę warstw papieru.
Origami let kwadrat (x,y) = if 0.0 <= x && x <= 1.0 && 0.0 <= y && y <= 1.0 then 1 else 0;; let po_lewej ((x1,y1), (x2,y2)) (x,y) =... let odbij ((x1,y1), (x2,y2)) (x,y) =... let zloz k l = fun p-> if po_lewej l p then 0 else k p + k (odbij l p);; let origami l = fold zloz kwadrat l;;
Procedury wyższych rzędów, c.d. Fold, to tylko jeden z typowych schematów przetwarzania list. Mapa let map f l = fold_right (fun h t -> (f h)::t) l [];; Filtr let filter p l = fold_right (fun h t -> if p h then h::t else t) l [];;
Przykład sito Eratostenesa let rec ints a b = if a > b then [] else a::ints (a+1) b;; let rec sito l = match l with [] -> [] h::t -> h::sito (filter(fun x->x mod h<>0) t);; let eratostenes n = sito (ints 2 n);;
Uleniwianie Wartość funkcji jest obliczana dopiero gdy znane są wartości argumentów. Można opóźnić moment obliczenia wartości funkcji, przez dodanie sztucznego argumentu. Odroczona wartość x, to: fun () -> x Wymuszenie obliczenia wartości: let force x = x ();; Uleniwianie ze spamiętywaniem.
Funkcje dużo wyższych rzędów Funkcje są wystarczająco silnym narzędziem, żeby za ich pomocą zaimplementować inne typy danych. Wartości logiczne. let prawda x y = x;; let falsz x y = y;; let i x y = x y falsz;; let lub x y = x prawda y;; let nie x a b = x b a;; let jesli w k a = force (w k a);;
Funkcje dużo wyższych rzędów Produkty kartezjańskie let pr x y = function f -> f x y;; let p1 p = p prawda;; let p2 p = p falsz;; Liczby naturalne Churcha. Liczbę n utorzsamiamy z podnoszeniem funkcji do n-tej potęgi. n=f f n
Procedury dużo wyższych rzędów let zero f = id;; let jeden f = f;; let inc n f = zloz (n f) f;; let plus m n f = zloz (m f) (n f);; let razy = zloz;;
Procedury dużo wyższych rzędów let test_zera x = x (function _ -> falsz) prawda;; let dec n f x = p1 (n (fun p -> pr(p2 p) (f(p2 p))) (pr x x));; let minus m n = (n dec) m;; let wieksze m n = nie (test_zera (minus m n));;
Strumienie Strumień = lista z uleniwionym ogonem Podstawowe operacje: Cons (głowa, ogon) Nil = pusty strumień head (strumień) = głowa tail (strumień) = ogon Strumienie mogą być nieskończone i mogą być definiowane rekurencyjnie.
Strumień liczb pierwszych Schemat sita Eratostenesa
Strumień liczb pierwszych, c.d. let rec sito s = Cons (head s, sito (sitko (head s) (tail s)));; let primes = sito (integers_from 2);; let primes = sito (integers_from 2);;
Strumień liczb pierwszych, c.d. drugie podejście let rec primes = Cons (2, filter_stream prime (integers_from 3)); and prime n = let rec iter ps = in if square (head ps) > n then true else if divisible n (head ps) then false else iter (tail ps) iter primes