PARADYGMATY I JĘZYKI PROGRAMOWANIA Programowanie funkcyjne ()
Treść 2 Python wprowadzenie klasy podprogramy generatory iteratory funkcja lambda funkcje apply, map, filter, reduce, eval etc. Programowanie funkcjonalne wstęp WFPM pierwiastek kwadratowy z liczby obliczanie pochodnej całkowanie
3 Python w skrócie (Patrz D.M. Beazley: Python. EssenZal reference, Sams 2006)
Python funkcyjny 4 Python funkcje i struktury, które można wykorzystać w programowaniu funkcyjnym; możliwość rekurencji n list lista n tuple krotka (n- tka) n lambda lambda args : expression n apply apply(funcname, [, args [, kwargs]]) n eval a = eval( 3*math.sin(3.5+x) + 7.2 ) n map b = map(lambda x: 3*x, a) n zip zszywanie wyrażeń iterowalnych (list, krotek) n reduce b = reduce(sum, a) n filter c = filter(lambda x: x < 4, a) n reverse, extend, insert, pop, remove, count, sort,... n iteratory n generatory (yield) n wyrażenia generatorowe
Python w skrócie 5 funkcje, argumenty, zwracane wartości wiele def factor(a): d = 2 while (d <= (a/2)): if ((a/d)*d == a): return ((a/d),d) d = d + 1 return (a,1) Wywołanie x, y = factor(1234) (x, y) = factor(1234)
Python klasy 6 Definicja class Circle(object): def init (self,radius): self.radius = radius def getarea(self): return math.pi*self.radius**2 def setarea(self,area): self.radius = math.sqrt(area/math.pi) area = property(getarea, setarea, doc= area of circle ) Tworzenie obiektów, dostęp a=circle(rad); print a.getarea() dziedziczenie
Python klasy 7 Dziedziczenie class A(object): def method1(self): print Class A : method1 class B(A): # Inherits from A def method1(self): print Class B : method1 def method2(self): print Class B : method2 class C(B): # Inherits from B def method3(self): print Class C: method 3 class D(A): def method1(self): print Class D: method 1 c = C() # Tworzenie egz. C c.method3() # Wywołanie C.method3(c) c.method1() # Wywołanie B.method1(c) e = E() # Tworzenie egz. E e.method1() # Wywołanie B.method1(e) class E(B,D): # Dziedziczy od B i D #(dziedziczenie wielokrotne) pass
Python generatory 8 Generatory i yield. Generator jest funkcją, która produkuje ciąg wyników zamiast jednej wartości def countdown(n): while n > 0: yield n n -= 1 >>> for i in countdown(5):... print i,... 5 4 3 2 1 >>> Generowanie ciągu wartości zapewnia instrukcja yield
Python w skrócie 9 inne zachowanie niż w przypadku zwykłej funkcji wywołanie generatora tworzy obiekt generatora, ale go nie uruchamia def countdown(n): print Odliczanie w dół od", n while n > 0: yield n n -= 1 >>> x = countdown(10) >>> >>> x <generator object at 0x58490> >>> Definicja generatora Tworzenie generatora... nic się nie dzieje Informacje o obiekcie
Python w skrócie 10 Generator uruchamia metoda next() >>> x.next() Odliczanie w dół od 10 10 >>> yield oblicza wartość i zawiesza wykonanie Funkcja wznawia wykonanie po następnym wywołaniu metody next() >>> x.next() 9 >>> x.next() 8... >>> x.next() 1 >>> x.next() Traceback (most recent call last): File "<stdin>", line 1, in? StopIteraZon >>>
Python w skrócie 11 Generatory przetwarzanie potokowe wejście generator generator... generator for x in s Idea: ciąg generatorów przetwarza sekwencję s z pomocą pętli for Tworzenie współprogramów (zaawansowane!, patrz Internet) z użyciem metody send() Filtry
12 Programowanie funkcyjne (PF) Wstęp
Problemy 13 Funkcje Modularyzacja Rekurencja Sklejanie Leniwe obliczanie Literatura: Why funczonal programming malers. J. Hughes. StackOvelflow (wfpm; programy w Haskell, Scheme)
Co to jest PF? 14 Nazwa PF pochodzi stąd, że podstawową operacją jest aplikowanie funkcji do argumentów. Główny program jest funkcją, która otrzymuje na wejściu swoje argumenty, przetwarza je i podaje wynik obliczeń, który też może być funkcją. Program zbudowany jest z innych funkcji, te jeszcze z innych itd. aż do momentu gdy funkcje stają się podstawowymi elementami języka. Dwie główne cechy PF Funkcje wyższego rzędu Leniwe obliczanie
Co to jest PF? 15 Charakterystyki PF potoczne Brak instrukcji przypisania zmienne raz określone nie zmieniają sią Brak efektów ubocznych funkcje obliczają tylko siebie à eliminacja błędów à nie jest ważna kolejność obliczeń (ponieważ efekty uboczne nie zmieniają wartości wyrażeń można je obliczać w dowolnym czasie) à zmienne można więc zastępować ich wartościami i odwrotnie programy są referencyjnie transparentne Programy są wielokrotnie krótsze niż te w językach imperatywnych à programiści są więc bardziej efektywni niż programiści, stosujący języki imperatywne
Co to jest PF? 16 Analogia z definicją programów strukturalnych, podobna charakterystyka przez wskazanie cech, których brak np. brak goto, bloki mają pojedyncze wejście i wyjście, bardziej matematyczna struktura. Główna różnica między programowaniem strukturalnym i niestrukturalnym to MODULARNOŚĆ, która zapewnia dużą wydajność w procesie programowania; moduły mogą być wielokrotnie używane; testowanie programów jest prostsze; łatwiej zlokalizować błędy; prostsza kompilacja częściowa. Zapomniana cecha języków strukturalnych: najpierw dzielimy problem na podproblemy, a następnie je ze sobą sklejamy w całość! PF Odpowiednia modularyzacja i odpowiednie składanie prostych części (funkcji) w całość sklejanie funkcji; moduły można wykorzystać powtórnie
Modularyzacja i sklejanie przykład 17 Definicja listy (cons jest tutaj funkcją składania) list X := nil cons X (list X) lista [] oznacza nil, lista pusta; [1] oznacza cons 1 nil; [1,2,3] oznacza cons 1 (cons 2 (cons 3 nil)) Sumowanie sum nil = 0 sum (cons numb list) = numb + sum list sum sum można zmodularyzować, wprowadzając operację reduce: sum = reduce add 0 gdzie add jest funkcją dwuargumentową: add x y = x + y add Wstawiając definicję funkcji sum otrzymamy rekurencyjną definicję reduce: (reduce add x) nil = x (reduce add x) (cons a l) = add a ((reduce add x) l) reduce (wyrażenie w nawiasach zastąpiło sum; nawiasy można opuścić)
Pewne funkcje 18 Można uogólnić ostatnią formułę zamieniając add na f: reduce f x nil = x reduce f x (cons a l) = f a (reduce f x l) Funkcja trójargumentowa (tutaj reduce), zastosowana do dwu argumentów jest traktowana, jak funkcja tylko trzeciego argumentu; w ogólności funkcja n argumentowa działając na m<n argumentów staje się funkcją m-n argumentową. Możemy teraz (bez dadatkowego programowania) użyć reduce do obliczenia iloczynu (lub zbadać czy lista zawiera element true, czy też wszystkie elementy true): product = reduce multiply 1 anytrue = reduce or false alltrue = reduce and true
Pewne funkcje 19 reduce można też rozumieć jako operację, która w liście zastępuje cons przez f, a nil przez a: Lista [1,2,3] oznacza: cons (1 cons (2 cons 3 nil))) Operacja reduce add 0 powoduje zamianę listy na: add (1 add (2 (add 3 0))) = 6 (zamiana cons -> add, nil -> 0) Podobnie reduce multiply 1 daje: multiply (1 multiply (2 (multiply 3 1))) = 6 Widać również, że reduce cons nil kopiuje listę. Ponieważ do listy można dodać inną listę przez operację cons, to widać też, że następująca funkcja append dodaje elementy do listy: append a b = reduce cons b a
Pewne funkcje 20 Sprawdzimy na przykładzie: append [1,2] [3,4] = reduce cons [3,4] [1,2] = (reduce cons [3,4]) (cons 1 (cons 2 nil)) = cons 1 (cons 2 [3,4])) (zastąpiono cons przez cons, a nil przez [3,4]) = [1,2,3,4] Funkcja, która podwaja elementy listy może być zapisana jako doubleall = reduce doubleandcons nil gdzie doubleandcons num list = cons (2*num) list Funkcję doubleandcons można jeszcze bardziej zmodularyzować: po pierwsze jako doubleandcons = fandcons double double n = 2*n fandcons f el list = cons (f el) list i następnie fandcons f = cons. f gdzie. oznacza złożenie funkcji f.g h = f(g h)
Pewne funkcje 21 Sprawdźmy poprawność fandcons: fandcons f el = (cons. f) el = cons (f el) czyli fandcons f el list = cons (f el) list Końcowa postać doubleall: doubleall = reduce (cons. double) nil Kolejna modularyzacja prowadzi do doubleall = map double map f = reduce (cons. f) nil gdzie map stosuje dowolną funkcję f do wszystkich elementów listy.
Ogólny program obliczeń w PF 22 f program; f(dane) Jeśli program f dostarcza danych do programu g, to cały program obliczeń ma postać: g(f(dane)) Mogłoby się zdarzyć, że f produkuje tak dużo danych, że nie ma miejsca na ich przechowywanie. Jest jednak tak, że program f dostarcza dane na żądanie, a więc oblicza ich tyle ile potrzebuje g to właśnie nazywamy leniwym obliczaniem.
Przykład 23 Algorytm Newtona (Herona) dla pierwiastka kwadratowego z liczby N Pierwsze przybliżenie: x = a (jakieś) następne: x = ½ (x+n/x) Algorytm Herona- Newtona- Raphsona sqrt(n): Dane jest pole N kwadratu. Obliczyć bok a. a i kolejne: x n+1 = ½ (x n +N/x n dowód (lepszy; szereg Taylora) x n+1 = (x n + N/x n )/2 Pole N N/a Jeśli w granicy mamy zbieżność, tzn. x n -> a: a =(a+n/a)/2 czyli 2a = a+n/a, a = N/a a*a = N stąd a = squareroot(n) Ponieważ oba boki o długościach a, N/a są przybliżeniem, więc ich średnia wartość (średnia arytmetyczna) jest bliższa prawdy...
Przykład cd. 24 Program (Python) def squareroot(n,a,eps=1e-6): x = a y = a+2*eps while abs(x-y)>eps: y = x x=1/2*(x+n/x) return x Program ten, zapisany w imperatywnym języku, nie da sie podzielić na mniejsze jednostki (?)
Przykład cd. 25 To samo w języku funkcyjnym będziemy generować kolejne przybliżenia funkcją next N x = (x + N/x) / 2 Jeśli oznaczymy tę funkcję przez f, to ciąg kolejnych przybliżeń jest postaci (list): [a, f a, f(f a), f(f(f a)),...] zdefiniujemy funkcję, która generuje ten ciąg: repeat f a = cons a (repeat f (f a)) listę kolejnych przybliżonych wartości pierwiastka obliczymy następująco repeat (next N) a
Przykład cd. 26 repeat jest przykładem funkcji o nieskończonej liczbie wyników ale ponieważ będziemy potrzebować tylko tyle wyników ile wymaga dokładność obliczeń, węc chcemy by repeat generowało kolejne przybliżenia na żądanie, a więc leniwie (nie wszystko od razu) Aby ten efekt uzyskać zdefiniujemy funkcję within, która będzie kontrolować dokładność obliczeń (parametr eps) within eps (cons a (cons b rest)) = = b, if abs(a-b) <= eps = within eps (cons b rest), if not Sklejając wszystko, mamy następujący program: sqrt a eps N = within eps (repeat (next N) a)
Python squareroot 27 def next(n,a): while True: a = (a + N/a) / 2.0 yield a Program obliczający my_sqrt(4, 1, 1e-6) print x, x*x # => 2 def within(eps,generator,*args): g = generator(*args) a,b = g.next(), g.next() while abs(a-b) > eps: return b a,b = b, g.next() def my_sqrt(n,guess,eps): return within(eps,next,n,guess) Nie jest to czysto funkcyjny program (!) ale posiada cechy programowania funkcyjnego (leniwa ewaluacja, listy, generatory)
Scheme squareroot 28 (require (lib "stream.ss" "srfi" "40")) (define (next_c N) (lambda (a) (/ (+ a (/ N a)) 2.0))) (define (repeat f init) (stream-cons init (repeat f (f init)))) (define (within eps stream) (let ((a (stream-car stream)) (b (stream-car (stream-cdr stream)))) (if (<= (abs (- a b)) eps) b (within eps (stream-cdr stream))))) (define (my-sqrt guess eps N) (within eps (repeat (next_c N) guess)))
Haskell squareroot 29 -- 4.1 Newton-Raphson square roots next n x = (x + n/x)/2.0 -- -- this is "iterate::(a->a)->a->[a]" -- repeat f a = a : iterate f (f a) within eps (a:b:rest) = if abs(a-b) <= eps then b else within eps (b:rest) sqroot a eps n = within eps (iterate (next n) a) relative eps (a:b:rest) = if abs(a-b) <= eps*abs(b) then b else relative eps (b:rest) relativesqrt a eps n = relative eps (iterate (next n) a)
Przykład. Python. Pochodna f(x) 30 Dysponując wcześniej zbudowanymi funkcjami możemy obliczać np. pochodne, całki itp. z funkcji, z zadaną dokładnością (eps) W pierwszym przybliżeniu, pochodna numeryczna funkcji f(x) jest dana jako iloraz różnicowy df/dx (f(x+h) f(x))/h Dla rozsądnych h (krok) można to poprawić, licząc df/dx dla kroku mniejszego, h/2. Proces powtarzamy aż do uzyskania zadanej dokładności obliczeń eps
Program (schemat funkcyjny) 31 easydiff f x h = (f(x+h)-f x) / h differentiate h0 f x = map (easydiff f x) (repeat halve h0) halve x = x/2 obliczenia z dokładnością eps: within eps (differentiate h0 f x)
Program lepsza wersja 32 Kolejne przybliżenia a n, a n+1 zawierają błędy postaci B*h**n, B stała (Wynika to z analizy szeregu Taylora) Mamy więc równania (dla kolejnych wartości 2h i h): a n = A + B 2 n h n a n+1 = A + B h n Stąd, poprawiona wartość wyniku A jest równa: A = 2n a n+1 2 n 1 Nie znamy n. Jak obliczyć n? a n
cd 33 Wartość n (nie będziemy tego dowodzić; z trzech kolejnych wyników dla kolejnych n; patrz algorytmy): n(a, b, c) = int log 2 a b c c 1 order (cons a (cons b (cons c rest))) = round(log2( (a-c)/(b-c) - 1 ))
cd 34 Tutaj round x oznacza zaokrąglenie x do najbliższej liczby całkowitej log2 x jest logarytmem o podstawie 2 z x Błędy częściowo eliminuje więc funkcja: elimerror n (cons a (cons b rest)) = = cons ((b*(2**n)-a)/(2**n-1)) (elimerror n (cons b rest)) Ciąg poprawionych przybliżeń dostaniemy z: improve s = elimerror (order s) s I pochodna jest dana przez: within eps (improve (differentiate h0 f x))
cd 35 Lepsze przybliżenie dostaniemy, powtarzając improve within eps (improve (improve (improve (differentiate h0 f x)))) Zdefiniujmy funkcję: super s = map second (repeat improve s) second (cons a (cons b rest)) = b Ostatecznie możemy zapisać: within eps (super (differentiate h0 f x)) mamy coraz lepsze i lepsze przyblżenia... Opisany algorytm jest bardzo złożony i w Pythonie jest o wiele bardziej skomplikowany itd.
Python algorytm Newtona 36 def next(n,a): while True: a = (a + N/a) / 2.0 yield a def within(eps,generator,*args): g = generator(*args) a,b = g.next(), g.next() while abs(a-b) > eps: a,b = b, g.next() return b def my_sqrt(n,guess,eps): return within(eps,next,n,guess) x=my_sqrt(4, 1, 1e-10) print x, x*x
Python (dokładniej) 37 from math import log, floor, sin, cos def halve(x): return x/2 def round(x): return floor(x+0.5) def order(a,b,c): return round(log((a-c)/(b-c)-1, 2)) # generator def easydiff(function,x,h): while True: diff=(function(x+h)-function(x))/h h=halve(h) yield diff def within(eps,generator,*args): g = generator(*args) a,b = g.next(),g.next() while abs(a-b) > eps: return b a,b = b,g.next() def differentiate(function,x,eps): h=100*eps return within(eps,easydiff,function,x,h) if name ==' main ': eps=0.0001; x=2. print x,differentiate(cos,x,eps), -sin(x)
Zadania: pochodne i całkowanie 38 Proszę napisać program poprawionego obliczania pochodnej w Pythonie Proszę napisać program całkowania funkcji f(x) na przedziale [a,b] metodą trapezów lub metodą Simpsona z wykorzystaniem funkcji, które zdefiniowaliśmy na wykładzie (w języku quasi- funkcjonalnym) Ulepszyć ten program całkowania, wykorzystując funkcję ellimerroer Napisać funkcję flatten, która dowolną listę (krotkę) przekształca w listę bez elementów iterowalnych (w skalary)
za tydzień...!? 39 w- 3 06.03.2013 12:04
Haskell algorytm Newtona 40 -- Newton-Raphson square roots next n x = (x + n/x)/2.0 -- -- this is "iterate::(a->a)->a->[a] -- repeat f a = a : iterate f (f a) within eps (a:b:rest) = if abs(a-b) <= eps then b else within eps (b:rest) sqroot a0 eps n = within eps (iterate (next n) a0) relative eps (a:b:rest) = if abs(a-b) <= eps*abs(b) then b else relative eps (b:rest) relativesqrt a0 eps n = relative eps (iterate (next n) a0)
Haskell pochodna 1 41 easydiff f x h = (f (x+h) - f x) / h differentiate h0 f x = map (easydiff f x) (iterate (/2) h0) -- diff1a h0 eps f x = within eps (differentiate h0 f x) diff1 h0 eps f = within eps. differentiate h0 f elimerror n (a:b:rest) = (b*(2**n)-a)/(2**n-1) : elimerror n (b:rest) -- need fromintegral to make a non-integer out of the Int which comes out of round order (a:b:c:rest) = fromintegral (round (logbase 2 ((a-c)/(b-c)-1)))
Haskell pochodna 2 42 improve s = elimerror (order s) s --diff2a h0 eps f x = within eps (improve (differentiate h0 f x)) diff2 h0 eps f = within eps. improve. differentiate h0 f -- super s = map second (iterate improve s) -- how can we make this point-free? super :: (RealFrac t, Floating t) => [t] -> [t] -- w/o this it wants to be [double]->[double] super = map second. iterate improve -- second (a:b:rest) = b second = head. tail diff3 h0 eps f = within eps. super. differentiate h0 f
Haskell całka 43 -- integration easyintegrate f a b = (f a + f b)*(b-a)/2 -- addpair becomes (uncurry (+)) integrate f a b = integ f a b (f a) (f b) integ f a b fa fb = (fa+fb)*(b-a)/2 : map (uncurry (+)) (zip (integ f a m fa fm) (integ f m b fm fb)) where m = (a+b)/2 fm = f m -- test: following should be about pi approxpi eps = within eps (improve (integrate (\x -> 4/(1+x*x)) 0 1)) superpi eps = within eps (super (integrate (\x -> 4/(1+x*x)) 0 1))
Scheme 44 (require (lib "stream.ss" "srfi" "40")) (define (next_c N) (lambda (a) (/ (+ a (/ N a)) 2.0))) (define (repeat f init) (stream-cons init (repeat f (f init)))) (define (within eps stream) (let ((a (stream-car stream)) (b (stream-car (stream-cdr stream)))) (if (<= (abs (- a b)) eps) b (within eps (stream-cdr stream))))) (define (my-sqrt guess eps N) (within eps (repeat (next_c N) guess)))