Wykład 3 Funkcje wyższych rzędów

Podobne dokumenty
Wykład 3 Funkcje wyższych rzędów

Programowanie funkcyjne. Wykªad 13

Programowanie funkcyjne Wykład 13. Siła wyrazu rachunku lambda

Teoretyczne Podstawy Języków Programowania Wykład 4. Siła wyrazu rachunku λ

Programowanie Funkcyjne. Marcin Kubica Świder,

Wykład 5 Listy leniwe

R. D. Tennent, The Denotational Semantics of Programming Languages [1976]

Paradygmaty programowania

Wykład 7 Porównanie programowania funkcyjnego i imperatywnego

Wstęp do Programowania potok funkcyjny

Programowanie funkcjonalne

Programowanie funkcyjne wprowadzenie Specyfikacje formalne i programy funkcyjne

Wstęp do programowania

Wstęp do programowania

Zaawansowane algorytmy i struktury danych

Indukcja. Materiały pomocnicze do wykładu. wykładowca: dr Magdalena Kacprzak

Programowanie obiektowe

Wstęp do Programowania potok funkcyjny

Wstęp do Programowania potok funkcyjny

PoniŜej znajdują się pytania z egzaminów zawodowych teoretycznych. Jest to materiał poglądowy.

Podstawowe algorytmy i ich implementacje w C. Wykład 9

REKURENCJA W JĘZYKU HASKELL. Autor: Walczak Michał

5. Algebra działania, grupy, grupy permutacji, pierścienie, ciała, pierścień wielomianów.

Wstęp do programowania

λ parametry. wartość funkcji suma = λ x y. x + y kwadrat = λ x. x * x K.M. Ocetkiewicz, 2008 WETI, PG 2 K.M. Ocetkiewicz, 2008 WETI, PG 3

Technologie informacyjne Wykład VII-IX

Wstęp do programowania. Różne różności

Wstęp do Programowania potok funkcyjny

Programowanie strukturalne. Opis ogólny programu w Turbo Pascalu

Zbiory, relacje i funkcje

Obliczenia na stosie. Wykład 9. Obliczenia na stosie. J. Cichoń, P. Kobylański Wstęp do Informatyki i Programowania 266 / 303

Przykładowe zadania z teorii liczb

Składnia funkcji i Rekurencja w języku Haskell

Algorytmy i Struktury Danych

Informatyka 1. Wyrażenia i instrukcje, złożoność obliczeniowa

Logika Stosowana. Wykład 1 - Logika zdaniowa. Marcin Szczuka. Instytut Informatyki UW. Wykład monograficzny, semestr letni 2016/2017

Wstęp do Programowania potok funkcyjny

Wstęp do Programowania potok funkcyjny

Języki i Paradygmaty Programowania

Wstęp do programowania

Lab 10. Funkcje w argumentach funkcji metoda Newtona. Synonimy nazw typów danych. Struktury. Tablice struktur.

Technologie Informatyczne Wykład VII

Podstawy programowania funkcjonalnego

Wstęp do Programowania potok funkcyjny

Szablony funkcji i klas (templates)

Ćwiczenia z wyliczania wartości funkcji

Języki programowania zasady ich tworzenia

Elementy języka Scheme

Programowanie. Lista zadań nr 15. Na ćwiczenia 11, 19 i 23 czerwca 2008

2.2. Gramatyki, wyprowadzenia, hierarchia Chomsky'ego

Informatyka I. Wykład 3. Sterowanie wykonaniem programu. Instrukcje warunkowe Instrukcje pętli. Dr inż. Andrzej Czerepicki

1. Nagłówek funkcji: int funkcja(void); wskazuje na to, że ta funkcja. 2. Schemat blokowy przedstawia algorytm obliczania

1. Wykład NWD, NWW i algorytm Euklidesa.

Poprawność semantyczna

Programowanie w VB Proste algorytmy sortowania

Semantyka i Weryfikacja Programów - Laboratorium 3

Pascal - wprowadzenie

Obiektowy Caml. Paweł Boguszewski

Języki programowania Haskell

Programowanie w języku C++ Podstawowe paradygmaty programowania

Języki i metody programowania

Programowanie komputerowe. Zajęcia 3

Analiza algorytmów zadania podstawowe

Wstęp do Programowania potok funkcyjny

Matematyka dyskretna. Andrzej Łachwa, UJ, /15

1 Automaty niedeterministyczne

Metalogika (1) Jerzy Pogonowski. Uniwersytet Opolski. Zakład Logiki Stosowanej UAM

Logika i teoria mnogości Wykład 14

Redis, skrypty w języku Lua

Dystrybucje, wiadomości wstępne (I)

Podstawy programowania. 1. Operacje arytmetyczne Operacja arytmetyczna jest opisywana za pomocą znaku operacji i jednego lub dwóch wyrażeń.

SIMR 2016/2017, Analiza 2, wykład 1, Przestrzeń wektorowa

Zasada indukcji matematycznej

Chcąc wyróżnić jedno z działań, piszemy np. (, ) i mówimy, że działanie wprowadza w STRUKTURĘ ALGEBRAICZNĄ lub, że (, ) jest SYSTEMEM ALGEBRAICZNYM.

Zadanie 1 Przygotuj algorytm programu - sortowanie przez wstawianie.

Jerzy Nawrocki, Wprowadzenie do informatyki

Wykład 9 Funktory (moduły sparametryzowane)

Rekurencja (rekursja)

Wstęp do programowania. Procedury i funkcje. Piotr Chrząstowski-Wachtel

P. Urzyczyn: Materia ly do wyk ladu z semantyki. Uproszczony 1 j. ezyk PCF

ALGEBRA Z GEOMETRIĄ PIERŚCIEŃ WIELOMIANÓW

vf(c) =, vf(ft 1... t n )=vf(t 1 )... vf(t n ).

Wieczorowe Studia Licencjackie Wrocław, Wykład nr 6 (w oparciu o notatki K. Lorysia, z modyfikacjami) Sito Eratostenesa

Elementy języka Scheme

Jak należy się spodziewać, mamy. Zauważmy jednak, że nie zachodzi równość

Wykład 6 Programowanie imperatywne w języku OCaml

Funkcje. Deklaracja funkcji. Definicja funkcji. Wykorzystanie funkcji w programie.

ALGORYTMY I STRUKTURY DANYCH

Programowanie obiektowe

Wstęp do programowania

Wstęp do programowania

Wstęp do Programowania potok funkcyjny

ZALICZENIE WYKŁADU: 30.I.2019

Wstęp do programowania. Drzewa. Piotr Chrząstowski-Wachtel

Język ludzki kod maszynowy

Rekurencja. Przygotowała: Agnieszka Reiter

Monoidy wolne. alfabetem. słowem długością słowa monoidem wolnym z alfabetem Twierdzenie 1.

wykład II uzupełnienie notatek: dr Jerzy Białkowski Programowanie C/C++ Język C - funkcje, tablice i wskaźniki wykład II dr Jarosław Mederski Spis

1 Wprowadzenie do algorytmiki

Programowanie - wykład 4

Transkrypt:

Wykład 3 Funkcje wyższych rzędów Funkcje jako dane Rozwijanie i zwijanie funkcji Składanie funkcji Funkcjonały dla list Funkcje wyższego rzędu jako struktury sterowania Semantyka denotacyjna prostego języka imperatywnego Rekonstrukcja typu Zdzisław Spławski Programowanie funkcyjne 1

Funkcje jako dane Języki programowania funkcyjnego traktują funkcje tak samo jak obiekty danych innych typów, a więc możliwe są: funkcje, produkujące funkcje jako wyniki; funkcje jako argumenty innych funkcji; struktury danych (np. krotki, listy) z funkcjami jako składowymi. Funkcja operująca na innych funkcjach jest nazywana funkcją wyższego rzędu lub funkcjonałem (ang. functional, stąd functional programming, a po polsku programowanie funkcyjne lub funkcjonalne). Zdzisław Spławski Programowanie funkcyjne 2

Funkcje jako dane Funkcje są często przekazywane jako dane w obliczeniach numerycznych. m Poniższy funkcjonał oblicza sumę 1 f ( i). Dla efektywności wykorzystuje on i= 0 rekursję ogonową. let sigma f m = let rec suma (i,z)= if i=m then z else suma(i+1, z+.(f i)) in suma(0, 0.0) ;; val sigma : (int -> float) -> int -> float = <fun> W połączeniu z funkcjonałami wygodne okazuje się wykorzystanie funkcji 9 2 anonimowych. Możemy obliczyć np. k =0 k bez konieczności oddzielnego definiowania funkcji podnoszenia do kwadratu. sigma (function k -> float(k*k)) 10;; - : float = 285. Zdzisław Spławski Programowanie funkcyjne 3

Funkcje jako dane 3 4 Wykorzystując ten funkcjonałłatwo obliczyć np. i j. = = + i 0 j 0 # sigma (function i -> sigma (function j -> float(i+j)) 5) 4;; -: float = 70. W przypadku częstego sumowania elementów macierzy można łatwo uogólnić m to na 1 n 1 g( i, j), czyli i= 0 j= 0 # let sigma2 g m n = sigma (function i -> sigma (function j -> g(i,j)) n) m;; val sigma2 : (int * int -> float) -> int -> int -> float = <fun> Wartość 3 4 = = i + i 0 j 0 j można teraz wyliczyć prościej: # sigma2 (function (k,l) -> float(k+l)) 4 5;; - : float = 70. Zdzisław Spławski Programowanie funkcyjne 4

Rozwijanie i zwijanie funkcji Na wykładzie 2. była mowa o postaci rozwiniętej (ang. curried) i zwiniętej (ang. uncurried) funkcji. Możemy napisać funkcjonał curry (odp. uncurry), który bierze funkcję w postaci zwiniętej (odp. rozwiniętej) i zwraca tę funkcję w postaci rozwiniętej (odp. zwiniętej). # let curry f x y = f (x, y);; (* Rozwijanie funkcji *) (* let curry = function f -> function x -> function y -> f (x,y);;*) val curry : ('a * 'b -> 'c) -> 'a -> 'b -> 'c = <fun> # let uncurry f (x, y) = f x y;; (* Zwijanie funkcji *) val uncurry : ('a -> 'b -> 'c) -> 'a * 'b -> 'c = <fun> # let plus (x,y) = x+y;; val plus : int * int -> int = <fun> # curry plus 4 5;; - : int = 9 # let add x y = x+ y;; (* lub let add = curry plus;; *) val add : int -> int -> int = <fun> # uncurry add (4,5);; - : int = 9 Zdzisław Spławski Programowanie funkcyjne 5

Składanie funkcji W matematyce często jest używana operacja złożenia (superpozycji) funkcji. Niech będą dane funkcje f :α β oraz g:γ α. Złożenie (f g):γ β jest definiowane (f g) (x) = f (g x). # let ($$) f g = fun x -> f ( g x);; (* Składanie funkcji *) val ( $$ ) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun> # let sqr x = x*x;; val sqr : int -> int = <fun> # (sqr $$ sqr) 2;; -: int = 16 # let third l3 = (List.hd $$ List.tl $$ List.tl) l3;; val third : 'a list -> 'a = <fun> # third [1;2;3;4;5];; -: int = 3 # let next_char = Char.chr $$ (+)1 $$ Char.code;; val next_char : char -> char = <fun> # next_char 'f';; - : char = 'g' Zdzisław Spławski Programowanie funkcyjne 6

Funkcjonały dla list - map Łącząc funkcje wyższych rzędów i parametryczny polimorfizm można pisać bardzo ogólne funkcjonały. Funkcjonał map aplikuje funkcję do każdego elementu listy: map f [x 1 ;... ; x n ]-> [f x 1 ;... ; f x n ] # let rec map f = function [] -> [] x::xs -> (f x) :: map f xs ;; val map : ('a -> 'b) -> 'a list -> 'b list = <fun> # map (function x -> x*x) [1;2;3;4];; -: int list = [1; 4; 9; 16] Ten funkcjonał jest zdefiniowany w module List: # List.map;; -: f:('a -> 'b) -> 'a list -> 'b list = <fun> # List.map String.length ["Litwo";"ojczyzno";"moja"];; - : int list = [5; 8; 4] Zdzisław Spławski Programowanie funkcyjne 7

Funkcjonały dla list - filter Funkcjonał filter aplikuje predykat do każdego elementu listy; jako wynik zwraca listę elementów spełniających ten predykat w oryginalnym porządku. # let rec filter pred = function [] -> [] x::xs -> if pred x then x::filter pred xs else filter pred xs ;; val filter : ('a -> bool) -> 'a list -> 'a list = <fun> Efektywniejszą implementację (z wykorzystaniem rekursji ogonowej ) można znaleźć w module List. # List.filter (function s -> String.length s <= 6) ["Litwo";"ojczyzno";"moja"];; - : string list = ["Litwo"; "moja"] Zdzisław Spławski Programowanie funkcyjne 8

Filter z rekursją ogonową # let filter_bad p = let rec find acc = function [] -> acc x :: xs -> if p x then find (acc@[x]) xs else find acc xs in find [];; val filter_bad : ('a -> bool) -> 'a list -> 'a list = <fun> (* Złożoność O(n 2 ). Nigdy w ten sposób! *) let filter p = let rec find acc = function [] -> List.rev acc x :: xs -> if p x then find (x :: acc) xs else find acc xs in find [];; (* Złożoność O(2n). Tylko tak! *) Zdzisław Spławski Programowanie funkcyjne 9

Generowanie liczb pierwszych metodą sita Eratostenesa Utwórz ciąg skończony [2,3,4,5,6,...,n]. Dwa jest liczbą pierwszą. Usuń z ciągu wszystkie wielokrotności dwójki, ponieważ nie mogą być liczbami pierwszymi. Pozostaje ciąg [3,5,7,9,11,...]. Trzy jest liczbą pierwszą. Usuń z ciągu wszystkie wielokrotności trójki, ponieważ nie mogą być liczbami pierwszymi. Pozostaje ciąg [5,7,11,13,17,...]. Pięć jest liczbą pierwszą itd. Na każdym etapie ciąg zawiera liczby mniejsze lub równe n, które nie są podzielne przez żadną z wygenerowanych do tej pory liczb pierwszych, więc pierwsza liczba tego ciągu jest liczbą pierwszą. Powtarzając opisane kroki otrzymamy wszystkie liczby pierwsze z zakresu [2..n]. # let primes to_n = let rec sieve n = if n <= to_n then n::sieve(n+1) else [] and find_primes = function h::t -> h:: find_primes (List.filter (function x -> x mod h <> 0) t) [] -> [] in find_primes(sieve 2);; val primes : int -> int list = <fun> # primes 30;; - : int list = [2; 3; 5; 7; 11; 13; 17; 19; 23; 29] Zdzisław Spławski Programowanie funkcyjne 10

Funkcjonały dla list - insert Funkcjonał insert bierze funkcję poprzedza (w postaci rozwiniętej) zadającą porządek elementów w liście, wstawiany element elem oraz listę uporządkowaną i wstawia elem do listy zgodnie z zadanym porządkiem. # let rec insert poprzedza elem = function [] -> [elem] h::t as l -> if poprzedza elem h then elem::l else h::(insert poprzedza elem t);; val insert : ('a -> 'a -> bool) -> 'a -> 'a list -> 'a list = <fun> Funkcję insert łatwo wyspecjalizować dla zadanego porządku, np. # let insert_le elem = insert (<=) elem;; val insert_le : 'a -> 'a list -> 'a list = <fun> # insert_le 4 [1;2;3;4;5;6;7;8];; - : int list = [1; 2; 3; 4; 4; 5; 6; 7; 8] Zdzisław Spławski Programowanie funkcyjne 11

Funkcjonały dla list uogólnienie typowych funkcji # let rec sumlist l = match l with h::t -> h + sumlist t (* funkcja f, której argumentami są głowa listy h i wynik wywołania rekurencyjnego definiowanej funkcji na ogonie t *) [] -> 0;; (* wynik acc definiowanej funkcji dla listy pustej [] *) val sumlist : int list -> int = <fun> Analogiczną strukturę miało wiele rozważanych przez nas funkcji na listach. Uogólniając tę funkcję i umieszczając f i acc jako dodatkowe argumenty otrzymujemy poniższy funkcjonał: # let rec fold_right f l acc = match l with h::t -> f h (fold_right f t acc) [] -> acc;; val fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun> Funkcję sumlist możemy teraz zdefiniować wykorzystując funkcjonał fold_right: # let sumlist l = fold_right (+) l 0;; val sumlist : int list -> int = <fun> Zdzisław Spławski Programowanie funkcyjne 12

Funkcjonały dla list fold_left (accumulate) fold_right (reduce) Funkcjonał fold_left aplikuje dwuargumentową funkcję f do każdego elementu listy (z lewa na prawo) akumulując wynik w acc (efektywnie rekursja ogonowa): fold_left f acc [x 1 ;... ; x n ]-> f(...(f(f acc x 1 )x 2 )...)x n # let rec fold_left f acc = function h::t -> fold_left f (f acc h) t [] -> acc;; val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun> Dualny funkcjonał fold_right aplikuje dwuargumentową funkcję f do każdego elementu listy (z prawa na lewo) akumulując wynik w acc (zwykła rekursja): fold_right f [x 1 ;... ; x n ] acc -> f x 1 (f x 2 (...(f x n acc)...)) # let rec fold_right f l acc = match l with h::t -> f h (fold_right f t acc) [] -> acc;; val fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun> Moduł List zawiera wiele użytecznych funkcjonałów dla list. Zdzisław Spławski Programowanie funkcyjne 13

fold_left (accumulate) - wykorzystanie # let sumlist = fold_left (+) 0;; (* sumowanie elementów listy *) val sumlist : int list -> int = <fun> # sumlist [4;3;2;1];; - : int = 10 # let prodlist = fold_left ( * ) 1;; (* mnożenie elementów listy *) val prodlist : int list -> int = <fun> # prodlist [4;3;2;1];; -: int = 24 # let flat l = fold_left (@) [] l;; (* spłaszczanie list *) val flat : 'a list list -> 'a list = <fun> # flat [[5;6];[1;2;3]];; -: int list = [5; 6; 1; 2; 3] (* konkatenowanie elementów listy napisów *) # let implode = fold_left (^) "";; val implode : string list -> string = <fun> # implode ["Ala ";"ma ";"kota"];; - : string = "Ala ma kota" Zdzisław Spławski Programowanie funkcyjne 14

Abstrakcja pomaga zauważyć i wyrazić analogie Bez wykorzystania odpowiednio wysokiego poziomu abstrakcji funkcyjnej trudno byłoby zauważyć analogie między funkcjami z poprzedniego slajdu. Struktura funkcji jest identyczna, ponieważ w każdym wypadku mamy do czynienia z monoidem: liczby całkowite, +, 0 liczby całkowite, *, 1 listy, @, [] napisy, ^, "" Operacja monoidu jest łączna, więc w omawianych wyżej funkcjach moglibyśmy użyć funkcjonału fold_right, np. # let imploder l = List.fold_right (^) l "";; val imploder : string list -> string = <fun> # imploder ["Ala ";"ma ";"kota"];; - : string = "Ala ma kota" Zdzisław Spławski Programowanie funkcyjne 15

Przykłady algebr abstrakcyjnych (homogenicznych) Półgrupa S, : S S S a (b c)=(a b) c (łączność) Monoid S,, 1 jest półgrupą z obustronną jednością (elementem neutralnym) 1:S a 1=a 1 a=a Grupa S,,, 1 jest monoidem, w którym każdy element posiada element odwrotny względem binarnej operacji monoidu : S S a a=1 a a=1 Zdzisław Spławski Programowanie funkcyjne 16

Matematykiem jest, kto umie znajdować analogie między twierdzeniami; lepszym, kto widzi analogie dowodów, i jeszcze lepszym, kto dostrzega analogie teorii, a można wyobrazić sobie i takiego, co między analogiami widzi analogie. Stefan Banach Zdzisław Spławski Programowanie funkcyjne 17

Funkcje wyższego rzędu jako struktury sterowania int silnia (int n) { int x,y; x=n; y=1; // utworzenie pary <n,1> while (x!= 0) // n-krotna iteracja { y = x*y; // operacji f takiej, x = x-1; // że f<x,y> = <x-1,x*y> } // return y; // wydzielenie drugiego elementu pary } Wykorzystując nieformalne komentarze, możemy formalnie zapisać tę funkcję w języku OCaml (z rekursją ogonową, co jest naturalne zważywszy, że komentarze dotyczą wersji iteracyjnej). let silnia' n = let rec f(x,y) = if x<>0 then f(x-1,x*y) else (x,y) in snd (f(n,1));; # silnia' 5;; - : int = 120 Zdzisław Spławski Programowanie funkcyjne 18

Składnia i semantyka prostego języka imperatywnego PJI Składnia abstrakcyjna V Zmienna N Liczba B ::= true false B && B B B not B E < E E = E E ::= N V E + E E * E E E -E C ::= skip C; C V := E if B then C else C fi while B do C od Semantykę denotacyjną języka zadaje się definiując funkcje semantyczne, które opisują, jak semantyka wyrażenia może być otrzymana z semantyki składowych tego wyrażenia (semantyka kompozycyjna). Funkcja semantyczna dla instrukcji C jest funkcja częściową «C, zmieniającą wektor stanu programu S (pamięć operacyjną). Dziedzinę S reprezentujemy za pomocą zbioru funkcji σ: Zmienna Z. Załóżmy dla uproszczenia, że funkcje semantyczne dla wyrażeń logicznych B i arytmetycznych E są znane. «B : S {true, false} «E : S Z Zdefiniujemy funkcję semantyczną «C : S Ã S dla instrukcji, a potem (pomijając pewne szczegóły formalne), zaprogramujemy ja w OCamlu. Zdzisław Spławski Programowanie funkcyjne 19

Składnia i semantyka prostego języka imperatywnego PJI Składnia abstrakcyjna C ::= skip C; C V := E if B then C else C fi while B do C od Semantyka denotacyjna «B : S {true, false} «E : S Z «skip σ = σ «C 1 ; C 2 σ '«C 2 («C 1 σ) Powyżej są używane dwa funkcjonały F : [ S Ã S ] [ S Ã S ] i fix : [[ S Ã S ] [ S Ã S ]] [ S Ã S ], znajdujący najmniejszy punkt stały zadanego funkcjonału. Jego istnienie gwarantuje teoria dziedzin. Zdzisław Spławski Programowanie funkcyjne 20

Funkcjonał dla punktu stałego W języku z mocną typizacją i wartościowaniem gorliwym (Ocaml, SML) można zdefiniować funkcjonał, znajdujący punkty stałe funkcji. W językach z mocną typizacją i wartościowaniem leniwym można zdefiniować ogólniejszy funkcjonał fix : ('a 'a) 'a, znajdujący punkty stałe np. list. # let fix f = let rec fixf x = f fixf x in fixf;; val fix : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun> # let f = fun f n -> if n=0 then 1 else n*f(n-1);; val f : (int -> int) -> int -> int = <fun> # let fact = fix f;; val fact : int -> int = <fun> # fact 3;; - : int = 6 # fact 4;; - : int = 24 Jak widać, najmniejszym punktem stałym funkcjonału f, skonstruowanym przez fix jest funkcja silnia. O punktach stałych i funkcjonałach, znajdujących punkty stałe, będzie mowa pod koniec semestru przy omawianiu rachunku lambda. Zdzisław Spławski Programowanie funkcyjne 21

Funkcje semantyczne dla PJI # let svar v s = List.assoc v s;; val svar : 'a -> ('a * 'b) list -> 'b = <fun> # let sskip s = s;; val sskip : 'a -> 'a = <fun> # let sscolon instr1 instr2 s = instr2 (instr1 s) (* czyli instr2 $$ instr1 *);; val sscolon : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c = <fun> # let sass var expr s = (var, expr)::(list.remove_assoc var s);; val sass : 'a -> 'b -> ('a * 'b) list -> ('a * 'b) list = <fun> # let sif b instr1 instr2 s = if b s then instr1 s else instr2 s;; val sif : ('a -> bool) -> ('a -> 'b) -> ('a -> 'b) -> 'a -> 'b=<fun> # let swhile b instr s = (fix (fun f b instr s -> if b s then f b instr (instr s) else s)) b instr s;; val swhile : ('a -> bool) -> ('a -> 'a) -> 'a -> 'a = <fun>. Zdzisław Spławski Programowanie funkcyjne 22

Semantyka denotacyjna silni Poniżej przedstawiono semantykę denotacyjną silni (cum grano salis :) # let call n = ("n",n)::("x",0)::("y",0)::[];; val call : int -> (string * int) list = <fun> # let sassx1 s = sass "x" (svar "n" s) s;; val sassx1 : (string * 'a) list -> (string * 'a) list = <fun> # let sassy1 s = sass "y" 1 s;; val sassy1 : (string * int) list -> (string * int) list = <fun> # let sloop = let sassy2 s = sass "y" ((svar "x" s)*(svar "y" s)) s and sassx2 s = sass "x" ((svar "x" s)-1) s in swhile (fun m -> (svar "x" m) <> 0) (sscolon sassy2 sassx2);; val sloop : (string * int) list -> (string * int) list = <fun> # let silnia = (svar "y") $$ sloop $$ sassy1 $$ sassx1 $$ call;; val silnia : int -> int = <fun> # silnia 3;; - : int = 6 # silnia 4;; - : int = 24 # silnia 5;; - : int = 120 Zdzisław Spławski Programowanie funkcyjne 23

# let f z y x = y (y z x);; Rekonstrukcja typu val f : 'a -> ('a -> 'b -> 'a) -> 'b -> 'b -> 'a = <fun> Skąd kompilator wziął ten typ? Formalnie typ powstał w wyniku rozwiązania układu równań w pewnej algebrze typów. Na podstawie ilości argumentów widzimy, że musi być f : α β γ δ. Z kontekstu użycia argumentu y:β widzimy, że y jest funkcją i otrzymujemy dwa równania: β=α γ ϕ oraz β=ϕ δ w których przez ϕ oznaczyliśmy typ wyniku funkcji y. Oczywiście β=α (γ ϕ)=ϕ δ, skąd otrzymujemy: α=ϕ oraz δ=γ ϕ=γ α czyli β=α γ α. Na argumenty x i z kontekst nie nakłada żadnych ograniczeń, więc ostatecznie f : α (α γ α) γ γ α. W praktyce do rekonstrukcji typu kompilator wykorzystuje algorytm unifikacji. Zdzisław Spławski Programowanie funkcyjne 24

Zadania kontrolne (1) 1. Podaj typy poniższych funkcji: a) let f1 x = x 1 1;; b) let f2 x y z = x ( y ^ z );; c) let f3 x y z = x y z;; d) let f4 x y = function z -> x::y;; 2. Zdefiniuj funkcje curry3 i uncurry3, przeprowadzające konwersję między zwiniętymi i rozwiniętymi postaciami funkcji od trzech argumentów. Podaj ich typy. 3. Dodajmy do języka PJI poniższe trzy rodzaje pętli, których semantyka znana jest intuicyjnie z języka Pascal. Zdefiniuj semantykę denotacyjną dla tych pętli, a potem zdefiniuj te funkcje semantyczne w OCamlu. a) repeat C until B b) for V:=E to E do C c) for V:=E to E do C 4. Przekształć poniższą rekurencyjną definicję funkcji sumprod, która oblicza jednocześnie sumę i iloczyn listy liczb całkowitych na równoważną definicję nierekurencyjną z jednokrotnym użyciem funkcji fold_left. let rec sumprod = function h::t -> let (s,p)= sumprod t in (h+s,h*p) [] -> (0,1);; Zdzisław Spławski Programowanie funkcyjne 25

Zadania kontrolne (2) 5. Poniższe dwie wersje funkcji quicksort działają niepoprawnie. Dlaczego? let rec quicksort = function [] -> [] [x] -> [x] xs -> let small = List.filter (fun y -> y < List.hd xs ) xs and large = List.filter (fun y -> y >= List.hd xs ) xs in quicksort small @ quicksort large;; let rec quicksort' = function [] -> [] x::xs -> let small = List.filter (fun y -> y < x ) xs and large = List.filter (fun y -> y > x ) xs in quicksort' small @ (x :: quicksort' large);; Zdefiniuj poprawną i efektywniejszą wersję (w której podział listy jest wykonywany w czasie liniowym w jednym przejściu) funkcji quicksort. 6. Zdefiniuj funkcje sortowania a) przez wstawianie z zachowaniem stabilności i złożoności O(n 2 ) insort : ( a-> a->bool) -> a list -> a list. b) przez łączenie (scalanie) z zachowaniem stabilności i złożoności O(n lg n) mergesort : ( a-> a->bool) -> a list -> a list. Pierwszy parametr jest funkcją, sprawdzającą porządek. Zamieść testy sprawdzające stabilność. Zdzisław Spławski Programowanie funkcyjne 26