Paradygmaty programowania - Katedra Informatyki > Home

Transkrypt

Paradygmaty programowania - Katedra Informatyki > Home
Dlaczego programowanie funkcyjne ?
• Języki imperatywne, a w duŜym stopniu takŜe
obiektowe, są bardzo silnie związane z architekturą
von Neumanna.
Paradygmaty programowania
Paradygmat funkcyjny
– WiąŜe się to z pojęciem stanu maszyny, któremu w językach
tych odpowiadają zmienne i dane w obiektach.
• Jedno z podejść do programowania wiąŜe się z
opinią, Ŝe związek z architekturą von Neumanna
narzuca niepotrzebne ograniczenia na proces
tworzenia oprogramowania.
• W zamian za to moŜna przyjąć paradygmat oparty na
pojęciu funkcji w znaczeniu takim, jakie od lat
przyjmuje się w matematyce.
Cezary Bolek
Katedra Informatyki
UŁ
1
Paradygmaty programowania
Popularność programowania funkcyjnego
Cezary Bolek
2
Cechy charakterystyczne
• W środowisku komercyjnych twórców
oprogramowania języki funkcyjne nie są zbytnio
rozpowszechnione – z pewnymi wyjątkami.
• Istnieje przykładowo Lisp. Jest to jeden z języków
funkcyjnych, który doczekał się sporej popularności.
• Uproszczonym i uporządkowanym dialektem Lispu
jest Scheme.
• W środowiskach akademickich znane i cenione są
takŜe np. języki ML wraz z pochodnymi i Haskell.
Słyną m.in. ze swojego silnego typowania.
• Oprócz tego dość silną pozycję mają pewne języki
„niszowe”, np. Erlang (w telekomunikacji) i K (w
obliczeniach finansowych).
Paradygmaty programowania
Cezary Bolek
• Nie ma pamięci, a zatem nie ma zmiennych
modelujących komórki pamięci.
• Nie ma teŜ więc „stanu” funkcji. A więc – nie ma
efektów ubocznych...
• Nie ma ciągów instrukcji i iteracji, a jedynie
rekurencja i wyraŜenia warunkowe. Innymi słowy,
funkcja (w sensie matematycznym) definiuje wartość,
ale nie określa ciągu działań na wartościach w
pamięci, które mogłyby ją wytworzyć.
• Parametr funkcji reprezentuje dowolny element z
dziedziny, lecz w trakcie obliczania jest ustalony
3
Paradygmaty programowania
Cezary Bolek
4
Rachunek Lambda
Rachunek Lambda
• λ -wyraŜenie zawiera:
• WaŜnym elementem podstaw matematycznych dla
programowania funkcyjnego jest rachunek lambda.
• Wymyślony przez Alonsa Churcha w roku 1941,
• Pozwala m.in. zdefiniować funkcję bez nadawania jej
nazwy.
• W klasycznym zapisie matematycznym definiując
funkcję, nadaje się jej z konieczności nazwę, np.:
– symbol λ,
– parametry funkcji,
– kropkę i wyraŜenie opisujące wartość funkcji;
• Niekiedy zamiast kropki stosuje się nawiasy
obejmujące parametry, np.
λ( x) x 2
f (n) = n f (n − 1)
• WyraŜenie takie moŜna zastosować do
parametru, pisząc parametr za owym
wyraŜeniem, np.:
• Natomiast wyraŜenie lambda („λ -wyraŜenie”)
utoŜsamiane jest z samą funkcją, np.:
λ x.x 2
(λ x.x 2 )(3) = 32 = 9
- funkcja obliczająca kwadrat danego parametru.
Paradygmaty programowania
Cezary Bolek
5
Cel stosowania języków funkcyjnych
6
• Zbiór funkcji pierwotnych.
• Zbiór funkcjonałów pozwalających
konstruować złoŜone funkcje z funkcji
pierwotnych.
Ponadto:
• Konstrukcje iteracyjne (np. pętla while) nie są
moŜliwe bez zmiennych, trzeba stosować rekurencję
• Wykonanie programu funkcyjnego nie ma stanu w
sensie semantyki operacyjnej lub denotacyjnej.
• Dla tych samych parametrów wykonanie funkcji
zawsze daje ten samy wynik. Określane jest to
mianem przeźroczystością odniesień
Cezary Bolek
Cezary Bolek
Składniki języka funkcyjnego
• Naśladowanie funkcji matematycznych
• (Czysty) język funkcyjny nie ma zmiennych –
programista nie musi się więc martwić o pamięć.
Paradygmaty programowania
Paradygmaty programowania
• Operacja aplikowania funkcji do argumentu.
• Pewne struktury do reprezentowania
parametrów i obliczonych wyników.
7
Paradygmaty programowania
Cezary Bolek
8
Lisp
• Najstarszy język funkcyjny.
• Prace projektowe rozpoczęły się pod koniec
lat 50-tych XX wieku. Pierwotnie jego nazwę
pisano duŜymi literami: LISP.
Przegląd języków funkcyjnych
• Ewoluował, choć obecne koncepcje języków
funkcyjnych są nieco inne.
• W historii istniało wiele dialektów Lispu
– obecnie do najpopularniejszych naleŜą dwa:
Common Lisp i Scheme
Paradygmaty programowania
Cezary Bolek
9
Lisp – obiekty danych
Cezary Bolek
Cezary Bolek
10
Lisp: S-wyraŜenia (Symbolic Expression)
• Wywołania funkcji zapisywane są w Odwrotnej
Notacji Polskiej, w postaci:
• W oryginalnym Lispie są tylko dwa moŜliwe
obiekty: atomy i listy
• Nie są to typy w sensie języka
imperatywnego
• Atomy to symbole (w postaci identyfikatorów)
lub stałe liczbowe
• Listy określa się przez wyszczególnienie
elementów w nawiasach
• Listy mogą być zagnieŜdŜane
• Listę pustą zapisuje się jako ( )
Paradygmaty programowania
Paradygmaty programowania
(nazwaFunkcji
arg1 ... argn)
• Definicje funkcji zapisuje się w notacji lambda, w
postaci:
(nazwaFunkcji (LAMBDA (arg1 ... argn) wyraŜenie))
• Pierwotnie S-wyraŜeniami nazywano określone w
powyŜszy sposób funkcje. Później nazwą tą objęto
wszystkie struktury w Lispie – dane i kod.
• WaŜna cecha Lispu: dane (listy) i kod (wywołania
funkcji) mają taką samą postać.
• Daje to m.in. moŜliwość dynamicznego tworzenia
kodu.
11
Paradygmaty programowania
Cezary Bolek
12
Scheme
Lisp - nawiasy
• Scheme jest odmianą Lispu. Pojawił się w
połowie lat 70-tych XX wieku
• Jest niewielki w porównaniu z oryginalnym
Lispem.
• Stosuje zakresy statyczne
• Funkcje mogą być wartościami wyraŜeń i
elementami list; mogą być przekazywane
jako parametry
• Interpreter Scheme’u
• S-wyraŜenia implikują stosowanie wielu
zagnieŜdŜonych konstrukcji opartych na nawiasach
• Sposób uŜycia nawiasów jest najlepiej widocznym na
pierwszy rzut oka faktem pozwalający odróŜnić Lisp
od innych rodzin języków
• Z tego powodu Lisp był często krytykowany, głównie
przez programistów innych języków
• JednakŜe składnia oparta na S-wyraŜeniach leŜy u
podstaw moŜliwości Lispu; jest niezwykle regularna,
co ułatwia jej przetwarzanie przez komputer
Paradygmaty programowania
Cezary Bolek
– Ma postać nieskończonej pętli, która czyta
wyraŜenie, interpretuje je i wyświetla wynik
– WyraŜenia są interpretowane przez funkcję EVAL
13
Scheme – pierwotne funkcje liczbowe
14
• Program to zbiór definicji funkcji
• MoŜna uŜywać funkcji bez nazw, np.
• (LAMBDA (x) (* x x))
– „+” dodaje wszystkie argumenty do siebie,
– „–” od pierwszego argumentu odejmuje pozostałe
• Zatem poniŜsze wywołanie wyświetli wynik 36:
((LAMBDA (x) (* x x)) 6)
• Przy wywołaniu bez argumentów zwracają
odpowiedni element neutralny
W powyŜszym λ-wyraŜeniu „x” jest zmienną związaną
• MoŜna związać nazwę z wartością lub
z λ-wyraŜeniem za pomocą specjalnej funkcji DEFINE.
– 0 dla dodawania,
– 1 dla mnoŜenia. itp.
– W pierwszym przypadku powstają nazwane stałe (nie zmienne!),
np. (DEFINE pi 3.1415926)
• Oprócz tego są istnieją:
ABS (wartość bezwzględna),
SQRT (pierwiastkowanie),
REMAINDER (reszta z dzielenia),
MIN, MAX
inne...
Paradygmaty programowania
Cezary Bolek
Scheme – definiowanie funkcji
• Są wśród nich podstawowe działania arytmetyczne
• Biorą dowolną liczbę argumentów, np.
–
–
–
–
–
Paradygmaty programowania
Cezary Bolek
– W drugim przypadku, jako parametry potrzebne są dwie listy:
prototyp funkcji i wyraŜenie (lub wyraŜenia), z którymi nazwa jest
wiązana. W tym przypadku pomija się słowo LAMBDA, np.
(DEFINE (square x) (* x x))
• Parametry przekazywane są przez wartość.
15
Paradygmaty programowania
Cezary Bolek
16
Scheme – funkcje do obsługi wyjścia
Scheme - predykaty działające na liczbach
• Typowy sposób wyprowadzenia wyników
programu to po prostu wyjście z interpretera,
czyli wynik aplikacji funkcji EVAL do funkcji w
programie
• Predykaty to wyraŜenia, które mają za zadanie
zwracać logiczną wartość zapytania które
reprezentują w zaleŜności od podanych parametrów
• Dostępne są predykaty: =, <>, >, <, >=, <=
• EVEN? sprawdza, czy podana wartość liczbowa jest
parzysta
• ODD? sprawdza, czy nieparzysta
• ZERO? sprawdza, czy wartość jest zerem
• NEGETIVE? sprawdza, czy podana wartość jest
ujemna
• Wartości logiczne są zapisywane jako
#F i #T
• Lista pusta () interpretowana jest jako #F, a dowolna
lista niepusta – jako #T.
• Istnieje takŜe imperatywna funkcja wyjściowa,
która wyświetla podane wyraŜenie (jest to
właściwie jej efekt uboczny...)
– (DISPLAY wyraŜenie)
• Druga funkcja imperatywna to (NEWLINE)
Paradygmaty programowania
Cezary Bolek
17
Scheme – struktury sterujące
18
• QUOTE zwraca swój parametr bez jakichkolwiek
zmian (czyli bez obliczania go jako funkcji); w tej roli
moŜna teŜ uŜyć jednego apostrofu,
np. (QUOTE (A B C)) jest równowaŜne
z ‘(A B C), czyli lista (A B C)
– Wybór „jeden z dwóch” zapisuje się w postaci:
(IF predykat wyraŜenieT wyraŜenieF)
• Wybór „jeden z wielu” zapisuje się w postaci:
(COND
– Funkcja ta jest potrzebna, gdy istnieje potrzeba np.
potraktowania listy jako „czystych” danych, a nie jako
wywołanie funkcji z parametrami. UŜycie nie zacytowanej
listy (A B C) skutkowałoby próbą policzenia funkcji A na
argumentach B i C.
(predykat1 wyraŜenie1)
(predykat2 wyraŜenie2)
...
(predykatn wyraŜenien)
(ELSE wyraŜenieE))
• CAR zwraca pierwszy element (głowę) podanej listy,
np. (CAR ‘(A B C)) zwraca A.
• Przykład:
– Wywołanie CAR dla pustej listy lub atomu jest błędem.
– Definicja funkcji obliczającej silnię:
(DEFINE (silnia n) (IF (= n 0) 1 (* n (silnia (– n 1)))))
Cezary Bolek
Cezary Bolek
Scheme – funkcje listowe
• Dwie struktury słuŜące do sterowania.
Paradygmaty programowania
Paradygmaty programowania
19
• CDR zwraca część listy pozostałą po usunięciu głowy
(ogon), np. (CDR ‘(A B C)) zwraca (B C).
Paradygmaty programowania
Cezary Bolek
20
Scheme – inne funkcje listowe i predykaty
Lisp - przykład
• CONS tworzy listę z podanej głowy i ogona, tzn.
wstawia pierwszy parametr jako nową głowę w liście
będącej drugim parametrem, np
• Funkcja sprawdzająca, czy atom (podany jak
pierwszy parametr) jest elementem listy
(podanej jako drugi parametr).
– (CONS ‘A ‘(B C)) zwraca (A B C)
– (CONS ‘(A B) ‘(C D)) zwraca ((A B) C D)
(DEFINE (member atm lst)
(COND
• LIST tworzy listę z dowolnej liczby parametrów. Jest
to skrótowa forma o takim samym znaczeniu jak
zagnieŜdŜone wywołania CONS
((NULL? lst) ‘())
((EQ? atm (CAR lst)) #T)
• Predykaty
– EQ? zwraca #T, jeśli obydwa parametry są atomami i są
równe
)
– LIST? zwraca #T, jeśli parametr jest listą
Cezary Bolek
21
Scheme – składanie funkcji
Cezary Bolek
22
• Operator aplikacji (zastosowania funkcji do
wszystkich elementów listy) zbiorowej moŜna
zdefiniować następująco:
– Wszelkie listy (nie cytowane za pomocą QUOTE
bądź apostrofu) są traktowane jako wywołania
funkcji
– Najpierw są wyliczane ich parametry
– Odnosi się to rekurencyjnie do list zagnieŜdŜonych
w listach
(DEFINE (mapcar fun lst)
(COND
((NULL? lst) ‘())
(ELSE (CONS (fun (CAR lst)) (mapcar fun (CDR lst))))
)
• Jest to w istocie właśnie składanie funkcji
• Przykład:
CDR (CDR ‘(A B C))) zwraca (C)
Cezary Bolek
Paradygmaty programowania
Scheme – składanie funkcji (przykład)
• Był to jedyny pierwotny funkcjonał w
pierwotnym Lispie.
• Działanie:
Paradygmaty programowania
(ELSE (member atm (CDR lst)))
#;> (member 'a '(a b c))
– NULL? zwraca #T, jeśli parametr jest listą pustą
Paradygmaty programowania
)
)
#;> (mapcar sin '(-1 0 1))
(-0.8414709848078965 0.0 0.8414709848078965)
23
Paradygmaty programowania
Cezary Bolek
24
Scheme – funkcje tworzące kod
Scheme – funkcje tworzące kod (przykład)
• Wariant 1: funkcja, która dodaje po kolei wszystkie atomy
• Program i dane w Scheme mają taką samą
strukturę
• To powoduje, Ŝe moŜna z łatwością
dynamicznie „budować” kod
• Przykład:
(DEFINE (sumator lst)
(COND
((NULL? lst) 0)
(ELSE (+ (CAR lst) (sumator (CDR lst))))
)
– Przypuśćmy, Ŝe mamy listę atomów liczbowych i
chcemy policzyć ich sumę.
– ZauwaŜmy, Ŝe operatora + nie moŜna zastosować
bezpośrednio, gdyŜ wymaga on jako parametrów
atomów, a nie listy.
– Istnieją dwie istotnie róŜne moŜliwości.
• Wariant 2: zbudowanie odpowiedniego wywołania dla operatora
+ za pomocą CONS i policzenie go poprzez wywołanie funkcji
EVAL:
(DEFINE (sumator lst)
(COND
((NULL? lst) 0)
)
Paradygmaty programowania
Cezary Bolek
(ELSE (EVAL (CONS ‘+ lst)))
26
Scheme – funkcja LET (przykład)
• Funkcja obliczająca iloraz sum dwóch list.
• Funkcja LET
• Składnia wywołania jest następująca:
(LET
(
(nazwa1 wyraŜenie1)
(nazwa2 wyraŜenie2)
...
(nazwan wyraŜenien)
)
ciało
)
• Wywołanie powoduje związanie podanych nazw z odpowiednimi
wyraŜeniami oraz obliczenie ciała
• Funkcja LET jest przydatna jako pewien sposób modularyzacji
obliczeń
Cezary Bolek
)
#;> (sumator1 '(1 2 3 4))
25
Scheme – wiązanie nazwy a wartością
Paradygmaty programowania
)
– Zwraca 0, jeśli druga suma (dzielnik) jest zerem
(DEFINE (iloraz x y)
(LET
(
(licznik (sumator x))
(mianownik (sumator y))
)
)
)
(IF (ZERO? mianownik) 0 (/ licznik mianownik))
#;> (iloraz '(-1 2 3) '(4 -5 6))
4/5
27
Paradygmaty programowania
Cezary Bolek
28
Język ML
ML – funkcje
• Deklaracje mają postać
fun nazwa (parametry) = ciało;
• Przykładowo:
• ML to język, który w swoim czasie był
typowym akademickim przykładem języka
funkcyjnego
• Stosuje zakresy statyczne.
• Jest silnie typowany, bez niejawnych
konwersji, za to stosuje niejawne nadawanie
typów.
• WyraŜenia arytmetyczne zapisywane w
tradycyjnej postaci infiksowej.
• Zawiera obsługę wyjątków.
Paradygmaty programowania
Cezary Bolek
fun square (x: int): int = x * x;
• Funkcje zawierające działania arytmetyczne nie
mogą być polimorficzne
• Funkcje zawierające tylko operacje na listach,
operatory n-tek i porównania (= oraz <>) mogą być
polimorficzne
29
Paradygmaty programowania
ML – funkcje
Cezary Bolek
30
ML – listy
• Listy zapisywane są w nawiasach kwadratowych,
np. [3, 2, 7]
• Lista pusta zapisywana jest jako [] lub nil
• Konstruktor listowy (odpowiednik CONS) – ::,
np. 1 :: [3, 2, 7] daje [1, 3, 2, 7]
• Elementy listy muszą być tego samego typu
• Funkcje wydzielające głowę i ogon (odpowiedniki
CAR i CDR): to hd oraz tl
• Operator :: moŜe być uŜywany w dopasowaniach
do wzorca, np.:
• MoŜna stosować konstrukcję „if-then-else”, np.
fun fact(n: int): int = if n = 0 then 1 else n * fact(n–1);
• Alternatywą jest stosowane często
dopasowywanie do wzorca, np.
fun fact(0)
= 1
| fact(n: int): int = n * fact(n–1);
fun length([])
= 0
| length(h :: t) = 1 + length(t);
Paradygmaty programowania
Cezary Bolek
31
Paradygmaty programowania
Cezary Bolek
32
ML – przykład
Haskell
• Funkcja łącząca dwie listy.
• Zrealizowana jako rekurencyjna, z
dopasowywaniem za pomocą operatora ::
• Podobnie jak ML, stosuje zakresy statyczne
• Jest silnie typowany
• Stosuje obliczanie leniwe, tzn. nie oblicza
podwyraŜeń, dopóki nie jest to konieczne
• Zawiera ciekawe operacje „listotwórcze”
• Czysto funkcyjny, bez efektów ubocznych.
fun append([], lst)
= lst
| append(h :: t, lst) = h :: append(t, lst);
Paradygmaty programowania
Cezary Bolek
33
Paradygmaty programowania
Haskell – funkcje
Cezary Bolek
34
Haskell – listy
• Definicje funkcji są postaci np.
fact 0 = 1
fact n = n * fact (n–1)
W powyŜszym przykładzie stosowane jest
dopasowywanie.
• Alternatywnie moŜna uŜyć dozorów, np.
fact n
| n == 0
= 1
| n > 0
= n * fact (n–1)
• PowyŜsza forma to wyraŜenie warunkowe. MoŜe
zawierać część opatrzoną klauzulą otherwise,
obliczaną wówczas, gdy wszystkie dozory są
fałszywe.
Paradygmaty programowania
Cezary Bolek
• Listy zapisywane są w nawiasach
kwadratowych, np. [3, 2, 7]
• Lista pusta zapisywana jest jako []
• Listy moŜna łączyć za pomocą operatora ++,
np. [1, 3] ++ [2 ,7] daje [1, 3, 2, 7]
• Operator # podaje długość listy
• Za pomocą operatora .. moŜna określać
ciągi arytmetyczne, np.
– [1..5] daje [1, 2, 3, 4, 5]
– [3, 6..15] daje [3, 6, 9, 12, 15]
35
Paradygmaty programowania
Cezary Bolek
36
Haskell – listy
Haskell – operacje listotwórcze
• Jest to metoda opisu list, które reprezentują zbiory.
• Ogólna postać: [ wyraŜenie | kwalifikator ]
• Przykładowo: [ n * n | n ← [1..20] ] definiuje
listę kwadratów liczb od 1 do 20.
• Konstruktor listowy (odpowiednik CONS) - :
np. 1 : [3, 2, 7] daje [1, 3, 2, 7]
– Innymi słowy – „lista wszystkich n*n po n z zakresu od 1 do 20”
• Operator : moŜe być uŜywany w
dopasowaniach, np.:
• Kwalifikatorem moŜe być generator (jak w powyŜszym
przykładzie) lub warunek, np.:
dzielniki n = [ i | i <- [1..n div 2], n mod i == 0]
iloczyn []
= 1
iloczyn (a : x) = a * iloczyn x
• Przykład
– implementacja sortowania szybkiego:
sort []
= []
sort (h:t) = sort [b | b<-t, b<=h] ++ [h] ++ sort [b | b<-t, b>h]
Paradygmaty programowania
Cezary Bolek
37
Haskell – obliczanie leniwe
• Parametry funkcji obliczane są tylko wtedy, gdy jest
to potrzebne do obliczenia owej funkcji
• Jeśli funkcja ma np. dwa parametry, a w konkretnym
wywołaniu pierwszy parametr nie jest potrzebny w
obliczeniach, odpowiedni parametr aktualny nie
będzie obliczany
• Jeśli potrzebna jest tylko część parametru, tylko ta
część zostanie obliczona
• Pozwala to definiować i stosować struktury
nieskończone
• Struktury takie nie są faktycznie tworzone, lecz mogą
być wykorzystane w leniwych obliczeniach
• Trzeba oczywiście uwaŜać, by nie zmusić Haskella
do nieskończonych obliczeń
Paradygmaty programowania
Cezary Bolek
39
Paradygmaty programowania
Cezary Bolek
38