Wykład 7 Porównanie programowania funkcyjnego i imperatywnego

Podobne dokumenty
Programowanie funkcjonalne

Wstęp do Programowania potok funkcyjny

Wykład 8. Moduły. Zdzisław Spławski Programowanie funkcyjne 1

Wykład 6 Programowanie imperatywne w języku OCaml

Strategia "dziel i zwyciężaj"

Obiektowy Caml. Paweł Boguszewski

Wstęp do Programowania potok funkcyjny

Wykład 5 Listy leniwe

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

Programowanie funkcyjne wprowadzenie Specyfikacje formalne i programy funkcyjne

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

Wstęp do Programowania potok funkcyjny

Paradygmaty programowania

Programowanie w VB Proste algorytmy sortowania

Podstawy algorytmiki i programowania - wykład 6 Sortowanie- algorytmy

Wstęp do Programowania potok funkcyjny

Wstęp do programowania

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

Wstęp do Programowania potok funkcyjny

Wstęp do Programowania potok funkcyjny

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

Wykład 5 Wybrane zagadnienia programowania w C++ (c.d.)

procesów Współbieżność i synchronizacja procesów Wykład prowadzą: Jerzy Brzeziński Dariusz Wawrzyniak

Semantyka i Weryfikacja Programów - Laboratorium 3

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

Języki i techniki programowania Ćwiczenia 2

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

Sortowanie przez scalanie

Algorytmy i Struktury Danych.

Technologie cyfrowe semestr letni 2018/2019

Podstawy programowania w języku C++

Podstawy programowania 2. Temat: Drzewa binarne. Przygotował: mgr inż. Tomasz Michno

Metodyka i Technika Programowania 1

Algorytm. a programowanie -

Wykład 9 Funktory (moduły sparametryzowane)

Programowanie Funkcyjne. Marcin Kubica Świder,

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Wykład 2. Poprawność algorytmów

Wykład 3 Składnia języka C# (cz. 2)

Definicja. Ciąg wejściowy: Funkcja uporządkowująca: Sortowanie polega na: a 1, a 2,, a n-1, a n. f(a 1 ) f(a 2 ) f(a n )

Sortowanie danych. Jolanta Bachan. Podstawy programowania

Wstęp do informatyki

Liczbowe funkcje rekurencyjne wielu zmiennych uczymy się na błędach

Programowanie funkcyjne. Wykªad 13

Wstęp do programowania

Wstęp do programowania 2

Podstawy informatyki. Informatyka stosowana - studia niestacjonarne. Grzegorz Smyk. Wydział Inżynierii Metali i Informatyki Przemysłowej

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

kiedy znowu uzyska sterowanie, to podejmuje obliczenie od miejsca, w którym poprzednio przerwała, i z dotychczasowymi wartościami zmiennych,

Podstawy programowania. Wykład: 13. Rekurencja. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

WYKŁAD 9. Algorytmy sortowania elementów zbioru (tablic) Programy: c4_1.c... c4_3.c. Tomasz Zieliński

Podstawy programowania w języku C

Programowanie Niskopoziomowe

Zadanie 1 Przygotuj algorytm programu - sortowanie przez wstawianie.

Opis zagadnieo 1-3. Iteracja, rekurencja i ich realizacja

Wstęp do Programowania potok funkcyjny

Algorytmy sortujące i wyszukujące

Wstęp do Programowania potok funkcyjny

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

Szablony funkcji i szablony klas

Wstęp do Programowania potok funkcyjny

Wprowadzenie do programowania

Programowanie obiektowe

Programowanie obiektowe

java.util.* :Kolekcje Tomasz Borzyszkowski

ZASADY PROGRAMOWANIA KOMPUTERÓW

Podstawy programowania. Wykład 7 Tablice wielowymiarowe, SOA, AOS, itp. Krzysztof Banaś Podstawy programowania 1

Pętle. Dodał Administrator niedziela, 14 marzec :27

Wykład 11 Podtypy a dziedziczenie

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

Jeśli chcesz łatwo i szybko opanować podstawy C++, sięgnij po tę książkę.

Wstęp do Programowania potok funkcyjny

Szablony funkcji i klas (templates)

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

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

Analiza algorytmów zadania podstawowe

ALGORYTMY I STRUKTURY DANYCH

Podstawy programowania

Informatyka 1. Przetwarzanie tekstów

Materiał Typy zmiennych Instrukcje warunkowe Pętle Tablice statyczne Wskaźniki Tablice dynamiczne Referencje Funkcje

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

Wykład 3. Metoda dziel i zwyciężaj

Programowanie w języku C++ Podstawowe paradygmaty programowania

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

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

STL: Lekcja 1&2. Filozofia STL

Redis, skrypty w języku Lua

Algorytmy i Struktury Danych.

Algorytmy i złożoności. Wykład 3. Listy jednokierunkowe

Sortowanie. LABORKA Piotr Ciskowski

Algorytmy i struktury danych

Teoretyczne podstawy informatyki

Język programowania PASCAL

Wykład 5. Sortowanie w czasie liniowologarytmicznym

ZASADY PROGRAMOWANIA KOMPUTERÓW ZAP zima 2014/2015. Drzewa BST c.d., równoważenie drzew, kopce.

Programowanie. programowania. Klasa 3 Lekcja 9 PASCAL & C++

Wprowadzenie do złożoności obliczeniowej

Składnia funkcji i Rekurencja w języku Haskell

Sortowanie - wybrane algorytmy

Wstęp do Programowania potok funkcyjny

Transkrypt:

Wykład 7 Porównanie programowania funkcyjnego i imperatywnego Styl funkcyjny i imperatywny: główne różnice Rekursja czy iteracja Wyrażenie let czy sekwencja Przykład: sortowanie szybkie Dzielenie czy kopiowanie wartości Kryteria wyboru stylu Zdzisław Spławski Programowanie funkcyjne 1

Styl funkcyjny i imperatywny: główne różnice Języki programowania funkcyjnego i imperatywnego różnią się głównie sposobem wykonania programu i zarządzaniem pamięcią. Program funkcyjny jest wyrażeniem. W wyniku wykonania programu otrzymywana jest wartość tego wyrażenia. Kolejność wykonywania operacji i fizyczna reprezentacja danych nie wpływają na otrzymany wynik. Zarządzaniem pamięcią zajmuje się system, wykorzystując program odśmiecania. Program imperatywny jest ciągiem instrukcji, modyfikujących stan pamięci. Na każdym etapie wykonania kolejność wykonywania instrukcji jest ściśle określona. Programy imperatywne częściej manipulują wskaźnikami lub referencjami do danych (wartości), niż samymi wartościami. Zwykle wymagają one od użytkownika zarządzania pamięcią, co prowadzi często do błędów; jednak nic nie stoi na przeszkodzie w wykorzystaniu odśmiecacza pamięci (patrz Java). Języki imperatywne dają większą kontrolę nad wykonaniem programu i reprezentacją danych. Są bliższe architektury komputera, dzięki czemu programy mogą być efektywniejsze, ale też bardziej podatne na błędy. Języki funkcyjne pozwalają programować na wyższym poziomie abstrakcji, dzięki czemu dają większa pewność poprawności (w sensie zgodności ze specyfikacją) i bezpieczeństwa (braku błędów wykonania) programu; systemy typizacji (statyczne lub dynamiczne) tę pewność zwiększają. Poprawność i bezpieczeństwo programu jest coraz częściej dominującym kryterium. Język Java został stworzony zgodnie z zasadą, że efektywność nie może dominować nad bezpieczeństwem i otrzymany produkt jest pochodną tych założeń. Te kryteria są w coraz większym stopniu brane pod uwagę przez producentów oprogramowania. Zdzisław Spławski Programowanie funkcyjne 2

Styl funkcyjny # let rec map f l = match l with [] -> [] h::t -> (f h) :: (map f t) ;; val map : ('a -> 'b) -> 'a list -> 'b list = <fun> Program nie precyzuje, w jakiej kolejności mają być wartościowane wyrażenia (f h) i (map f t). Reprezentacja wewnętrzna list i sposób zarządzania pamięcią również nie muszą być znane. let lista = ["one"; "two"; "three"];; # val lista : string list = ["one"; "two"; "three"] let wynik = map (function x -> x) lista;; # val wynik : string list = ["one"; "two"; "three"] Obie listy zwierają takie same wartości, co można sprawdzić za pomocą operatora równości strukturalnej: # lista = wynik;; - : bool = true Fizycznie są to jednak dwie różne listy, co można sprawdzić za pomocą operatora równości fizycznej: # lista == wynik;; - : bool = false Zdzisław Spławski Programowanie funkcyjne 3

Styl imperatywny (1) Potraktujmy napis jako strukturę modyfikowalną i zmodyfikujmy jeden z napisów w liście wynik. # (List.hd wynik).[1] <- 'l';; - : unit = () # wynik;; - : string list = ["ole"; "two"; "three"] # lista;; - : string list = ["ole"; "two"; "three"] Jak widać, wykorzystywanie cech imperatywnych wymaga dokładnej znajomości reprezentacji wewnętrznej danych. Następny przykład pokazuje, że wykorzystanie cech imperatywnych wymaga dokładnej znajomości kolejności wartościowania wyrażenia. # type 'a ilist = { mutable c : 'a list } ;; type 'a ilist = { mutable c : 'a list; } # let icreate () = { c = [] } let iempty l = (l.c = []) let icons x y = y.c <- x::y.c ; y let ihd x = List.hd x.c let itl x = x.c <- List.tl x.c ; x ;; val icreate : unit -> 'a ilist = <fun> val iempty : 'a ilist -> bool = <fun> val icons : 'a -> 'a ilist -> 'a ilist = <fun> val ihd : 'a ilist -> 'a = <fun> val itl : 'a ilist -> 'a ilist = <fun> Zdzisław Spławski Programowanie funkcyjne 4

Styl imperatywny (2) # let rec imap f l = if iempty l then icreate() else icons (f (ihd l)) (imap f (itl l)) ;; val imap : ('a -> 'b) -> 'a ilist -> 'b ilist = <fun> # let lista = icons "one" (icons "two" (icons "three" (icreate()))) ;; val lista : string ilist = {c=["one"; "two"; "three"]} # imap (function x -> x) lista ;; Uncaught exception: Failure "hd". W funkcji imap wyrażenie (itl lista) było wartościowane przed wyrażeniem (ihd lista). W dodatku lista została opróżniona, mimo że funkcja imap nie obliczyła wyniku. lista;; -: string ilist = {c=[]} Wyjątek został spowodowany tym, że pozostawiliśmy systemowi wybór kolejności wartościowania wyrażeń. Możemy to poprawić. # let rec imap f l = if iempty l then icreate() else let h = ihd l in icons (f h) (imap f (itl l)) ;; val imap : ('a -> 'b) -> 'a ilist -> 'b ilist = <fun> Zdzisław Spławski Programowanie funkcyjne 5

Styl imperatywny (3) # let lista = icons "one" (icons "two" (icons "three" (icreate()))) ;; val lista : string ilist = {c=["one"; "two"; "three"]} # imap (function x -> x) lista ;; -: string ilist = {c=["one"; "two"; "three"]} Lista początkowa została jednak znów opróżniona, co jest spowodowane sposobem działania funkcji itl. # lista;; - : string ilist = {c=[]} Innym sposobem jawnego zadania kolejności wartościowania jest użycie sekwencji i pętli. # let imap f l = let l_wyn = icreate () in while not (iempty l) do ignore (icons (f (ihd l)) l_wyn) ; ignore (itl l) done ; { l_wyn with c = List.rev l_wyn.c } ;; val imap : ('a -> 'b) -> 'a ilist -> 'b ilist = <fun> # let lista = icons "one" (icons "two" (icons "three" (icreate()))) ;; val lista : string ilist = {c=["one"; "two"; "three"]} # imap (function x -> x) lista ;; - : string ilist = {c=["one"; "two"; "three"]} Wykorzystanie funkcji ignore:'a -> unit doskonale ilustruje fakt, że nie chodziło o wyniki funkcji, lecz o ich efekty uboczne. Zdzisław Spławski Programowanie funkcyjne 6

Rekursja czy iteracja (1) Często niesłusznie utożsamia się rekursję ze stylem funkcyjnym, a iterację z imperatywnym. Program czysto funkcyjny nie może być iteracyjny, ponieważ wartość warunku pętli nigdy się nie zmieni (przezroczystość referencyjna!). Jednak program imperatywny może być rekursywny, np. pierwsza wersja imap. Program rekursywny może spowodować przepełnienie stosu. # let rec succ n = if n=0 then 1 else 1 + succ (n-1);; val succ : int -> int = <fun> # succ 100000;; Stack overflow during evaluation (looping recursion?). W wersji iteracyjnej głębokość stosu nie zależy od argumentu. let succ_iter n = let i =ref 0 in for j=0 to n do incr i done;!i ;; val succ_iter : int -> int = <fun> # succ_iter 100000;; - : int = 100001 Zdzisław Spławski Programowanie funkcyjne 7

Rekursja czy iteracja (2) Jednak użycie rekursji ogonowej również nie zwiększa głębokości stosu. let succ_tr n = let rec aux n acc = if n=0 then acc else aux (n-1) (acc+1) in aux n 1 ;; val succ_tr : int -> int = <fun> # succ_tr 100000;; - : int = 100001 Rozpoznawanie rekursji ogonowej jest konieczne w czystych językach funkcyjnych. Zdzisław Spławski Programowanie funkcyjne 8

Który styl wybrać? Kryterium prostoty. Algorytm, który należy zaimplementować jest przedstawiony w postaci funkcyjnej lub imperatywnej i najprościej jest zachować ten styl w implementacji. Kryterium efektywności. Dobrze napisany program imperatywny jest zwykle bardziej efektywny niż jego funkcyjny odpowiednik (który zwykle jest prostszy i czytelniejszy), jednak komplikacja kodu nie zawsze jest uzasadniona (porównaj funkcje map oraz imap). Zdzisław Spławski Programowanie funkcyjne 9

Wyrażenie let czy sekwencja Widzieliśmy, że jeśli program wykorzystuje efekty uboczne, konieczne jest precyzyjne określenie kolejności wartościowania. Można to wykonać używając dwóch stylów. W wyrażeniu (f g) OCaml najpierw wartościuje g, następnie f (SML odwrotnie), a potem aplikuje wartość f do wartości g. Kolejność wartościowania można wymusić w obu stylach. Styl funkcyjny: let pom = g in f pom lub let pom = f in pom g Styl imperatywny: pom := g; f!pom lub pom := f; (!pom) g Zdzisław Spławski Programowanie funkcyjne 10

Idea algorytmu sortowania szybkiego Algorytm sortowania szybkiego (ang. quicksort) został zaproponowany przez C.A.R. Hoare a w 1962 r. W praktyce jest najszybszym algorytmem sortowania dla losowych ciągów. Jego konstrukcja wykorzystuje strategię dziel i zwyciężaj. Idea sortowania tablicy t[l..r] jest następująca: 1. Jeśli r-l 1 to tablica t jest posortowana. 2. Wybierz dowolny element rozdzielający v t (ang. pivot). 3. Dziel tablicę t[l..r] na dwie podtablice t[l..k] i t[k+1..r], takie że l i k. t[i] v oraz k+1 i r. v t[i]. 4. Zwyciężaj: sortuj w taki sam sposób obie podtablice t[l..k] i t[k+1..r]. Dla efektywności algorytmu kluczowy jest dobry wybór elementu rozdzielającego. W kroku 3 obie podtablice powinny mieć zbliżoną długość. Jako elementu dzielącego nie należy używać pierwszego ani ostatniego elementu tablicy! Można wybierać element środkowy, medianę z małej próbki, np. trzech wybranych elementów, bądź dokonywać wyboru losowo. Możliwe usprawnienia algorytmu sortowania szybkiego: 1. Zamiast włączać element rozdzielający do jednej z podtablic, wstawić go zawsze na właściwe miejsce na granicy obu podtablic i wykluczyć z dalszego sortowania. 2. Zastąpić rekursję iteracją (z jawnym użyciem stosu). 3. Sortować natychmiast krótszą podtablicę, a na stos odkładać żądanie posortowania dłuższej podtablicy. Dzięki temu wielkość stosu można ograniczyć do lg n. 4. Dla krótkich tablic, pozostałych do posortowania w ostatnim etapie działania algorytmu (są one w znacznym stopniu posortowane), używać algorytmu sortowania przez wstawianie. Zdzisław Spławski Programowanie funkcyjne 11

Sortowanie szybkie (1) # let swap tab i j = let aux = tab.(i) in tab.(i) <- tab.(j); tab.(j) <- aux;; val swap : 'a array -> int -> int -> unit = <fun> # let choose_pivot tab m n = tab.((m+n)/2);; val choose_pivot : 'a array -> int -> int -> 'a = <fun> # let partition tab l r = let i=ref l and j=ref r and pivot=choose_pivot tab l r in while!i <=!j do while tab.(!i) < pivot do incr i done; while pivot < tab.(!j) do decr j done; if!i <=!j then begin swap tab!i!j; incr i; decr j end done; (!i,!j) ;; val partition : 'a array -> int -> int -> int * int = <fun> Zdzisław Spławski Programowanie funkcyjne 12

Sortowanie szybkie (2) # let rec quick tab l r = if l < r then let (i,j) = partition tab l r in if j-l < r-i then let _ = quick tab l j in quick tab i r else let _ = quick tab i r in quick tab l j else ();; val quick : 'a array -> int -> int -> unit = <fun> #let quicksort tab = quick tab 0 ((Array.length tab)-1);; val quicksort : 'a array -> unit = <fun> # let t1 = [ 4;8;1;12;7;3;1;9 ];; val t1 : int array = [ 4; 8; 1; 12; 7; 3; 1; 9 ] # quicksort t1;; - : unit = () # t1;; - : int array = [ 1; 1; 3; 4; 7; 8; 9; 12 ] # let t2 = [ "kobyla";"ma";"maly";"bok" ];; val t2 : string array = [ "kobyla"; "ma"; "maly"; "bok" ] # quicksort t2;; - : unit = () # t2;; - : string array = [ "bok"; "kobyla"; "ma"; "maly" ] W funkcjach swap i partition wykorzystaliśmy styl imperatywny, natomiast w funkcji quick styl funkcyjny. Zdzisław Spławski Programowanie funkcyjne 13

Dzielenie czy kopiowanie wartości (1) Dopóki przetwarzane wartości nie są modyfikowalne, nie jest ważne, czy są one dzielone, czy nie. # let id x = x;; val id : 'a -> 'a = <fun> # let a = [ 1; 2; 3 ];; val a : int list = [1; 2; 3] # let b = id a;; val b : int list = [1; 2; 3] # a == b;; - : bool = true Nie jest istotne, czy b jest kopią listy a, czy jest tą samą (fizycznie) listą, ponieważ listy liczb całkowitych są wartościami stałymi. Gdyby elementami listy były wartości modyfikowalne, musielibyśmy wiedzieć, czy modyfikacja elementu jednej listy pociąga za sobą modyfikację elementu drugiej listy, tzn. czy elementy są dzielone. Implementacja polimorfizmu w OCamlu powoduje kopiowanie wartości bezpośrednich i dzielenie wartości strukturalnych. Argumenty są przekazywane do funkcji zawsze przez wartość (czyli są kopiowane), ale w przypadku wartości strukturalnych argumentami są wskaźniki (podobnie zachowuje się Java). Ilustruje to poniższy przykład. Zdzisław Spławski Programowanie funkcyjne 14

Dzielenie czy kopiowanie wartości (2) # let a = [ 1; 2; 3 ];; val a : int array = [ 1; 2; 3 ] # let b = id a;; val b : int array = [ 1; 2; 3 ] # a == b;; -: bool = true # a.(1) <- 4;; - : unit = () # a;; - : int array = [ 1; 4; 3 ] # b;; -: int array = [ 1; 4; 3 ] Wybór efektywniejszej (w konkretnym przypadku) reprezentacji danych należy do programisty (przynajmniej w językach OCaml, SML itp., w większości języków wybór jest dość ograniczony). W języku Java mamy napisy niemodyfikowalne (String) oraz modyfikowalne (StringBuffer). Z jednej strony, wybór wartości modyfikowalnych umożliwia efektywną manipulację tymi wartościami w miejscu (bez konieczności alokowania pamięci); z drugiej strony wykorzystanie wartości niemodyfikowalnych pozwala na bezpieczne ich dzielenie. Pokażemy to na przykładzie dwóch sposobów reprezentacji list. Zdzisław Spławski Programowanie funkcyjne 15

Dzielenie czy kopiowanie wartości (3) type 'a list_fixed = LFnil LFcons of 'a * 'a list_fixed;; type 'a list_mutable = LMnil LMcons of 'a * 'a list_mutable ref;; Listy stałe są równoważne listom w OCamlu, natomiast listy modyfikowalne odpowiadają listom w językach imperatywnych (Pascal, C), w których węzeł składa się z wartości oraz wskaźnika do następnego elementu listy. Dla list stałych istnieje tylko jeden sposób konkatenacji list, który wymusza skopiowanie struktury pierwszej listy, za to druga lista może być dzielona. # let rec concat l1 l2 = match l1 with LFnil -> l2 LFcons (a,l11) -> LFcons(a, (concat l11 l2)) ;; val concat : 'a list_fixed -> 'a list_fixed -> 'a list_fixed = <fun> Zdzisław Spławski Programowanie funkcyjne 16

Dzielenie czy kopiowanie wartości (4) # let lf1 = LFcons(1, LFcons(2, LFnil)) and lf2 = LFcons(3, LFcons(4, LFnil)) ;; val lf1 : int list_fixed = LFcons (1, LFcons (2, LFnil)) val lf2 : int list_fixed = LFcons (3, LFcons (4, LFnil)) # let lf3 = concat lf1 lf2 ;; val lf3 : int list_fixed = LFcons (1, LFcons (2, LFcons (3, LFcons (4, LFnil)))) # lf1==lf3 ;; - : bool = false # let tllf l = match l with LFnil -> failwith "Empty list" LFcons(_,x) -> x ;; val tllf : 'a list_fixed -> 'a list_fixed = <fun> # tllf(tllf(lf3)) == lf2 ;; - : bool = true Z przykładu widać, że pierwsze węzły list lf1 i lf3 są różne, podczas gdy druga część listy lf3 jest fizycznie równa lf2. Zdzisław Spławski Programowanie funkcyjne 17

Dzielenie czy kopiowanie wartości (5) W przypadku list modyfikowalnych możemy wybrać między utworzeniem nowej wartości (funkcja concat_copy) lub modyfikacją argumentu (funkcja concat_share). # let rec concat_copy l1 l2 = match l1 with LMnil -> l2 LMcons (x,l11) -> LMcons(x, ref (concat_copy!l11 l2)) ;; val concat_copy : 'a list_mutable -> 'a list_mutable -> 'a list_mutable = <fun> Funkcja concat_copy tworzy listę skonkatenowaną tak jak funkcja concat (kopiując listę pierwszą i dzieląc drugą). # let concat_share l1 l2 = match l1 with LMnil -> l2 _ -> let rec set_last = function LMcons(_,l) -> if!l=lmnil then l:=l2 else set_last!l LMnil -> failwith "concat_share : this is impossible!!" in set_last l1 ; l1 ;; val concat_share : 'a list_mutable -> 'a list_mutable -> 'a list_mutable = <fun> To rozwiązanie nie wymaga przydzielania dodatkowej pamięci, ale modyfikuje swoje argumenty, co może mieć wpływ na dalsze zachowanie programu. Zdzisław Spławski Programowanie funkcyjne 18

Dzielenie czy kopiowanie wartości (6) # let lm1 = LMcons(1, ref (LMcons(2, ref LMnil))) and lm2 = LMcons(3, ref (LMcons(4, ref LMnil))) ;; val lm1 : int list_mutable = LMcons (1, {contents=lmcons (2, {contents=lmnil})}) val lm2 : int list_mutable = LMcons (3, {contents=lmcons (4, {contents=lmnil})}) # let lm3 = concat_share lm1 lm2 ;; val lm3 : int list_mutable = LMcons (1, {contents= LMcons (2, {contents=lmcons (3, {contents=lmcons (4, {contents=lmnil})})})}) # lm1=lm3;; - : bool = true # lm1==lm3;; - : bool = true Zdzisław Spławski Programowanie funkcyjne 19

Kryteria wyboru stylu W programach czysto funkcyjnych zabronione jest używanie efektów ubocznych, co uniemożliwia wykorzystywanie wartości modyfikowalnych, wyjątków (i operacji wejścia/wyjścia).mniej restrykcyjna definicja pozwala na użycie funkcji, które nie modyfikują środowiska zewnętrznego, nawet jeśli wewnątrz funkcja używa wartości modyfikowalnych, a więc jest napisana w stylu imperatywnym. Dozwolone jest też zgłaszanie wyjątków. Taka funkcja widziana z zewnątrz jako czarna skrzynka jest nie do odróżnienia od funkcji napisanej w stylu czysto funkcyjnym (z wyjątkiem możliwości zgłoszenia wyjątku). Z drugiej strony, program w stylu imperatywnym korzysta ze wszystkich udogodnień oferowanych przez język OCaml: bezpieczeństwo statycznej typizacji, automatyczne zarządzanie pamięcią, mechanizm wyjątków, parametryczny polimorfizm i inferencja typów. Wybór stylu funkcyjnego bądź imperatywnego zależy od implementowanej aplikacji. Przy wyborze należy uwzględnić poniższe kryteria. Możliwość wyboru struktur danych (struktury modyfikowalne lub niemodyfikowalne). Narzucone struktury danych (modyfikowalne, rekursywne). Efektywność. Szybkość tworzenia i czytelność kodu. Styl funkcyjny pozwala na szybsze stworzenie czytelniejszego kodu; po zidentyfikowaniu sekcji krytycznych można je przepisać efektywniej, używając stylu imperatywnego. Zdzisław Spławski Programowanie funkcyjne 20