PARADYGMATY I JĘZYKI
PROGRAMOWANIA
Treść
¨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
(Patrz D.M. Beazley: Python. EssenZal
reference, Sams 2006)
Python w skrócie
3
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 : expressionn
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)
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()
Python – klasy
¨ 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)
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
>>>
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
¨
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
generator
generator
...
generator
Wstęp
Programowanie funkcyjne (PF)
12
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
¨ 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
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)
17
sum
reduce lista
Pewne funkcje
¨
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
¨
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
¨
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
consprzez
cons,a
nilprzez
[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
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
Przykład
¨
Algorytm Newtona (Herona) dla
pierwiastka kwadratowego z liczby
N
¤
Pierwsze przybliżenie:
x = a (jakieś)
¤
następne: x = ½ (x+N/x)
¤
i kolejne: x
n+1= ½ (x
n+N/x
n¨
„dowód”(lepszy; szereg Taylora)
x
n+1= (x
n+ N/x
n)/2
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
¨
Algorytm Herona-‐Newtona-‐
Raphsona
¨
sqrt(N)
: Dane jest pole N kwadratu.
Obliczyć bok 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...
23
a
N/a
Pole
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
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
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:
Python –
squareroot
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 ¨
Program obliczający
my_sqrt(4, 1, 1e-6)
print x, x*x # => 2
¨
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)
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
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
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:
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:
Nie znamy n. Jak obliczyć n?
A =
2
n
a
n+1
a
n
cd
33
Wartość n (nie będziemy tego dowodzić; z trzech kolejnych
wyników dla kolejnych n; patrz algorytmy):
order (cons a (cons b (cons c rest)))
=
round(log2( (a-c)/(b-c) - 1 ))
n(a, b, c) = int log
2
✓
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:
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))
Python – algorytm Newtona
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 36
Python (dokładniej)
from math import log, floor, sin, cosdef 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: a,b = b,g.next() return b 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) 37
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 39
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
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
Scheme
(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)))