Wykład 8
Transkrypt
Wykład 8
PARADYGMATY I JĘZYKI PROGRAMOWANIA Programowanie funkcyjne (w-‐8) 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 ¤ w-‐8 3 Python w skrócie (Patrz D.M. Beazley: Python. EssenZal reference, Sams 2006) w-‐8 Python funkcyjny 4 ¨ Python ¤ funkcje i struktury, które można wykorzystać w programowaniu funkcyjnym; możliwość rekurencji n n n n n n n n n n n n n w-‐8 list lista tuple krotka (n-‐tka) lambda lambda args : expression apply apply(funcname, [, args [, kwargs]]) eval a = eval(‘3*math.sin(3.5+x) + 7.2’) map b = map(lambda x: 3*x, a) zip zszywanie wyrażeń iterowalnych (list, krotek) reduce b = reduce(sum, a) filter c = filter(lambda x: x < 4, a) reverse, extend, insert, pop, remove, count, sort, ... iteratory generatory (yield) 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) w-‐8 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’) ¨ ¨ w-‐8 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” class E(B,D): # Dziedziczy od B i D #(dziedziczenie wielokrotne) pass w-‐8 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) 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 >>> ¨ w-‐8 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> >>> w-‐8 Definicja generatora Tworzenie generatora ... nic się nie dzieje Informacje o obiekcie Python w skrócie 10 ¨ ¨ ¨ w-‐8 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 ¨ w-‐8 12 Programowanie funkcyjne (PF) Wstęp w-‐8 Problemy 13 Funkcje ¨ Modularyzacja ¨ Rekurencja ¨ Sklejanie ¨ Leniwe obliczanie ¨ ¨ Literatura: ¤ Why funcZonal programming malers. J. Hughes. ¤ StackOvelflow (wfpm; programy w Haskell, Scheme) w-‐8 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 w-‐8 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 w-‐8 Co to jest PF? 16 ¨ ¨ w-‐8 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) [] 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 lista sum sum można zmodularyzować, wprowadzając operację reduce: sum = reduce add 0 gdzie add jest funkcją dwuargumentową: add x y = x + y 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) (wyrażenie w nawiasach zastąpiło sum; nawiasy można opuścić) w-‐8 add reduce Pewne funkcje 18 ¨ ¨ ¨ w-‐8 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 w-‐8 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) w-‐8 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. w-‐8 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. ¨ w-‐8 Przykład 23 ¨ Algorytm Newtona (Herona) dla pierwiastka kwadratowego z liczby N ¤ ¤ ¤ ¨ ¨ w-‐8 Pierwsze przybliżenie: x = a (jakieś) następne: x = ½ (x+N/x) i kolejne: xn+1 = ½ (xn+N/xn ¨ ¨ Pole N „dowód”(lepszy; szereg Taylora) xn+1 = (xn + N/xn)/2 Jeśli w granicy mamy zbieżność, tzn. xn -> a: a =(a+N/a)/2 czyli 2a = a+N/a, a = N/a a*a = N stąd a = squareroot(N) Algorytm Herona-‐Newtona-‐ Raphsona sqrt(N): Dane jest pole N kwadratu. Obliczyć bok a. a ¨ N/a 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 ¨ ¨ w-‐8 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 w-‐8 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 ¨ w-‐8 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 my_sqrt(4, 1, 1e-6) print x, x*x # => 2 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) w-‐8 Program obliczający ¨ 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))) w-‐8 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) w-‐8 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 ¨ w-‐8 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) w-‐8 Program – lepsza wersja 32 ¨ ¨ Kolejne przybliżenia an, an+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 × 2n × h n an+1 = A + B × hn Stąd, poprawiona wartość wyniku A jest równa: n 2 an+1 an A= 2n 1 Nie znamy n. Jak obliczyć n? w-‐8 cd 33 Wartość n (nie będziemy tego dowodzić; z trzech kolejnych wyników dla kolejnych n; patrz algorytmy): n(a, b, c) = int log2 ✓ a b c c ◆ 1 order (cons a (cons b (cons c rest))) = round(log2( (a-c)/(b-c) - 1 )) w-‐8 cd 34 ¨ ¨ ¨ ¨ w-‐8 Tutaj round x log2 x oznacza zaokrąglenie x do najbliższej liczby całkowitej 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 ¨ ¨ ¨ ¨ w-‐8 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 def my_sqrt(N,guess,eps): return within(eps,next,N,guess) yield a x=my_sqrt(4, 1, 1e-10) 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 w-‐8 print x, x*x Python (dokładniej) 37 from math import log, floor, sin, cos def within(eps,generator,*args): g = generator(*args) def halve(x): a,b = g.next(),g.next() return x/2 while abs(a-b) > eps: a,b = b,g.next() def round(x): return b return floor(x+0.5) def differentiate(function,x,eps): def order(a,b,c): h=100*eps return round(log((a-c)/(b-c)-1, 2)) # generator def easydiff(function,x,h): return within(eps,easydiff,function,x,h) if __name__=='__main__': eps=0.0001; while True: print x,differentiate(cos,x,eps), -sin(x) diff=(function(x+h)-function(x))/h w-‐8 x=2. h=halve(h) yield diff Zadania: pochodne i całkowanie 38 ¨ ¨ ¨ ¨ w-‐8 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 w-‐8 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) w-‐8 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) = w-‐8 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 w-‐8 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)) w-‐8 Scheme 44 (require (lib "stream.ss" "srfi" "40")) (define (within eps stream) (let ((a (stream-car stream)) (define (next_c N) (b (stream-car (stream-cdr stream)))) (lambda (a) (if (<= (abs (- a b)) eps) (/ (+ a (/ N a)) 2.0))) b (within eps (stream-cdr stream))))) (define (repeat f init) (stream-cons init (repeat f (f init)))) (define (my-sqrt guess eps N) (within eps (repeat (next_c N) guess))) w-‐8