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