Programowanie funkcyjne/Funktory: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Przemek (dyskusja | edycje)
Nie podano opisu zmian
 
(Nie pokazano 2 wersji utworzonych przez 2 użytkowników)
Linia 1: Linia 1:
==Wstęp==
==Wstęp==
Funktory to moduły sparametryzowane. Często jeden moduł ''importuje'' inne moduły.Skoro rozdzieliliśmy pojęcia interfejsu (sygnatury) i implementacji (struktury), to możemy zapisywać moduły korzystające z innych modułów w bardziej abstrakcyjny sposób. Jeżeli spojrzymy na sygnaturę jak na specyfikację wykorzystywanego modułu, to zgodnie z zasadą ukrywania informacji ''information hiding'', istotne powinny być jedynie specyfikacjewykorzystywanych modułów, a nie ich implementacje. Podstawienie dowolnej implementacji spełniającej zadaną specyfikację/sygnaturę powinno dać pożądane rezultaty. Moduł taki możemy zapisać w postaci sparametryzowanej --- podajemy sygnatury parametrów, po podstawieniu w ich miejsce odpowiednich implementacji otrzymujemy wynikowy moduł.
<p align="justify">
   
Zwykle jedne moduły korzystają z innych modułów.
Na funktory możemy też patrzeć jak na funkcje na strukturach --- przetwarzają one moduły parametry w moduły wynikowe.
Zgodnie z zasadą ukrywania informacji (ang. ''information hiding''), istotne powinny być jedynie specyfikacje wykorzystywanych modułów, a nie ich konkretne implementacje.
Podstawienie dowolnej implementacji o zadanej sygnaturze i spełniającej zadaną specyfikację powinno być
akceptowalne z punktu widzenia modułów korzystających z takiej implementacji.  
</p>


Przykład:
<p align="justify">
Kolejka priorytetowa. Na elementach kolejki musi być określony porządek liniowy.
Skoro rozdzieliliśmy pojęcia interfejsu (sygnatury) i implementacji (struktury), to możemy zapisywać moduły korzystające z innych modułów w bardziej abstrakcyjny sposób.
Zamiast określać dokładnie z jakiej implementacji korzysta dany moduł, określamy jedynie jej sygnaturę
(i przez domniemanie również specyfikację).
Otrzymujemy w ten sposób '''funktory'''.
</p>
 
<p align="justify">
Funktory to moduły sparametryzowane.
Ich parametrami są struktury o określonych sygnaturach, z których mają one korzystać.
Dopiero gdy podstawimy w miejsce parametrów konkretne struktury,
otrzymujemy strukturę implementowaną przez funktor.
</p>
 
<p align="justify">
Zwykłe moduły (struktury) tworzą statyczną sieć powiązań.
Natomiast moduły są jak wymienne elementy, klocki, które możemy łączyć na rozmaite sposoby.
</p>
 
<p align="justify">
Na funktory możemy też patrzeć jak na funkcje na strukturach --- przetwarzają one struktury-parametry w struktury wynikowe.
Należy jednak pamiętać, że podstawianie parametrów funktorów dokonuje się w sposób statyczny -- w czasie kompilacji.
</p>
 
== Proste funktory ==
<p align="justify">
Tak samo jak w przypadku struktur, w przypadku funktorów mamy pojęcie sygnatury.
Sygnatura funktora określa sygnaturę parametrów oraz sygnaturę wyniku.
</p>
 
<sygnatura> ::= <u>functor</u> <u>(</u> <Identyfikator> <u>:</u> <sygnatura> <u>)</u> <u>-></u> <sygnatura>
<struktura> ::= <u>functor</u> <u>(</u> <Identyfikator> <u>:</u> <sygnatura> <u>)</u> <u>-></u> <struktura>  |
                <struktura> <u>(</u> <struktura <u>)</u>
 
<p align="justify">
Jeżeli funktor ma więcej niż jeden parametr, to tak jak w przypadku procedur, zapisujemy go jako
funktor z pierwszym parametrem, którego wynikiem jest funktor z drugim parametrem itd.
Z powyższej składni widać też, że funktory mogą być nie tylko wynikiem, ale i parametrami funktorów,
czyli mamy funktory wyższych rzędów.
Zajmiemy się nimi dalej.
</p>
 
{{przyklad|[Kolejka priorytetowa]||}}
<p align="justify">
Przypuśćmy, że chcemy zaimplementować kolejkę priorytetową elementów bliżej nieokreślonego typu.  
Na elementach kolejki musi być określony porządek liniowy, zgodnie z którym będziemy szeregować elementy w kolejce.  
Kolejkę zaimplementujemy w postaci funktora.
Parametrem funktora będzie struktura określająca typ elementów i porządek liniowy na nich.
</p>
        
        
  '''type''' porownanie = Mniejsze | Rowne | Wieksze;;
  '''type''' porownanie = Mniejsze | Rowne | Wieksze;;
  &nbsp;&nbsp;
   
  '''module''' '''type''' PORZADEK_LINIOWY =  
  '''module''' '''type''' PORZADEK_LINIOWY =  
&nbsp;&nbsp;'''sig'''
  '''sig'''
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t
    '''type''' t                                 (* Typ elementów.    *)
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' porownaj : t -> t -> porownanie
    '''val''' porownaj : t -> t -> porownanie   (* Porządek liniowy. *)
&nbsp;&nbsp;'''end''';;
  '''end''';;
        
        
Kolejka priorytetowa powinna mieć sygnaturę postaci:
Kolejka priorytetowa powinna mieć sygnaturę postaci:
        
        
  '''module''' '''type''' PRI_QUEUE_FUNC = '''functor''' (Elem : PORZADEK_LINIOWY) ->  
  '''module''' '''type''' PRI_QUEUE_FUNC = '''functor''' (Elem : PORZADEK_LINIOWY) ->  
&nbsp;&nbsp;'''sig'''
  '''sig'''
&nbsp;&nbsp;&nbsp;&nbsp;'''exception''' EmptyQueue
    '''exception''' EmptyQueue
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' elem = Elem.t
    '''type''' elem = Elem.t               (* Typ elementów.                    *)
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' queue
    '''type''' queue                       (* Typ kolejki.                      *)
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' empty : queue  
    '''val''' empty : queue                 (* Pusta kolejka.                    *)
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' put : elem -> queue -> queue
    '''val''' put : elem -> queue -> queue (* Wstawienie do kolejki.            *)
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' min : queue -> elem
    '''val''' min : queue -> elem           (* Element najmniejszy.              *)
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' removemin : queue -> queue
    '''val''' removemin : queue -> queue   (* Usunięcie elementu najmniejszego. *)
&nbsp;&nbsp;'''end''';;
  '''end''';;
     
 
Zwróćmy uwagę na to, że typ <tt>queue</tt> jest abstrakcyjny,a <tt>elem</tt> nie.Ukrywamy w ten sposób strukturę kolejki. Natomiast cokolwiek będzie wiadome o typie <tt>Elem.t</tt>będzie nadal wiadome o typie <tt>elem</tt>.Jest to konieczne z tego powodu, że inaczej nie bylibyśmy włożyć żadnego elementu do kolejki. Implementację kolejki priorytetowej możemy zrealizować w następujący sposób:
<p align="justify"> 
     
Zwróćmy uwagę na to, że typ <tt>queue</tt> jest abstrakcyjny.
Ukrywamy w ten sposób strukturę kolejki.
Zwróćmy też uwagę na to, że typ <tt>elem</tt> jest określony jako <tt>Elem.t</tt>.
Jest to specyficzna konstrukcja.  
Przy jej zastosowaniu, cokolwiek będzie wiadome o typie <tt>Elem.t</tt>, będzie również wiadome o typie <tt>elem</tt>,  
mimo że w sygnaturze <tt>PORZADEK_LINIOWY</tt> typ <tt>t</tt> jest typem abstrakcyjnym.  
Implementację kolejki priorytetowej możemy zrealizować w następujący sposób:
</p>
 
  '''module''' PriQueue : PRI_QUEUE_FUNC =
  '''module''' PriQueue : PRI_QUEUE_FUNC =
&nbsp;&nbsp;'''functor''' (Elem : PORZADEK_LINIOWY) ->  
  '''functor''' (Elem : PORZADEK_LINIOWY) ->  
&nbsp;&nbsp;'''struct'''
  '''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' elem = Elem.t
    '''type''' elem = Elem.t
&nbsp;&nbsp;&nbsp;&nbsp;'''exception''' EmptyQueue
    '''exception''' EmptyQueue
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' queue = elem list
    '''type''' queue = elem list
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' empty = []
    '''let''' empty = []
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' '''rec''' put x q =  
    '''let''' '''rec''' put x q =  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''match''' q '''with'''  
      '''match''' q '''with'''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[] -> [x] |
        [] -> [x] |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h::t -> '''if''' Elem.porownaj x h = Mniejsze '''then'''  
        h::t -> '''if''' Elem.porownaj x h = Mniejsze '''then'''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x :: q
          x :: q
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''else'''
        '''else'''
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h::(put x t)
          h::(put x t)
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' min q =
    '''let''' min q =
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''match''' q '''with'''  
      '''match''' q '''with'''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[] -> '''raise''' EmptyQueue |
        [] -> '''raise''' EmptyQueue |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;h::_ -> h
        h::_ -> h
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' removemin (q:queue) =  
    '''let''' removemin (q:queue) =  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''match''' q '''with'''  
      '''match''' q '''with'''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[] -> '''raise''' EmptyQueue |
        [] -> '''raise''' EmptyQueue |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_::t -> t
        _::t -> t
&nbsp;&nbsp;'''end''';;
  '''end''';;
     
 
Kolejka priorytetowa liczb całkowitych może być zrealizowana w taki sposób:
<p align="justify"> 
     
Kolejka priorytetowa liczb całkowitych może być zrealizowana poprzez podstawienie jako argumentu funktora następującej struktury:
  '''module''' IntOrder : PORZADEK_LINIOWY =  
</p>
&nbsp;&nbsp;'''struct'''
 
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t = int
  '''module''' IntOrder =  
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' porownaj (x:int) (y:int) =  
  '''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''if''' x < y '''then''' Mniejsze '''else'''
    '''type''' t = int
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''if''' x > y '''then''' Wieksze '''else''' Rowne
    '''let''' porownaj (x:int) (y:int) =  
&nbsp;&nbsp;'''end''';;
      '''if''' x < y '''then''' Mniejsze '''else'''
&nbsp;&nbsp;
      '''if''' x > y '''then''' Wieksze '''else''' Rowne
  '''end''';;
 
  '''module''' IntQueue = PriQueue (IntOrder);;
  '''module''' IntQueue = PriQueue (IntOrder);;
     
W module <tt>IntOrder</tt> struktura typu <tt>t</tt> jest jawna. Dzięki temu będzie wiadomo, że elementy kolejki są typu <tt>int</tt>. Innym podstawowym pojęciem związanym z porządkami liniowymi jest sortowanie.
IntQueue.min (IntQueue.put 42 IntQueue.empty);;
''- : IntQueue.elem = 42''
 
<p align="justify"> 
Zwróćmy uwagę, że nie ograniczamy struktury <tt>IntOrder</tt> do sygnatury <tt>PORZADEK_LINIOWY</tt>.
Chcemy, aby typ <tt>t</tt> nie był typem abstrakcyjnym, lecz był równoważny <tt>int</tt>.  
Dzięki temu będzie wiadomo, że elementy kolejki są również typu <tt>int</tt>.  
</p>
 
<p align="justify">
Dla porównania spójrzmy na poniższy przykład.
Jest to definicja modułu, którego nie da się używać, gdyż typ elementów jest abstrakcyjny i nie jesteśmy w stanie utworzyć jakiegokolwiek elementu.
</p>
 
'''module''' SomeOrder : PORZADEK_LINIOWY =
  '''struct'''
    '''type''' t = int
    '''let''' porownaj (x:int) (y:int) =
      '''if''' x < y '''then''' Mniejsze '''else'''
      '''if''' x > y '''then''' Wieksze '''else''' Rowne
  '''end''';;
'''module''' SomeQueue = PriQueue (SomeOrder);;
SomeQueue.min (SomeQueue.put <u>42</u> SomeQueue.empty);;
''This expression has type int but is here used with type SomeQueue.elem = SomeOrder.t''
 
 
{{przyklad|[Sortowanie]||}}
<p align="justify"> 
Innym podstawowym pojęciem związanym z porządkami liniowymi jest sortowanie.
Również możemy je zapisać w postaci funktora sparametryzowanego porządkiem liniowym.
</p>
        
        
  '''module''' '''type''' SORTING = '''functor''' (Elem : PORZADEK_LINIOWY) ->  
  '''module''' '''type''' SORTING = '''functor''' (Elem : PORZADEK_LINIOWY) ->  
&nbsp;&nbsp;'''sig'''
  '''sig'''
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' elem = Elem.t
    '''type''' elem = Elem.t                   (* Typ elementów.                      *)
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' sortuj : elem list -> elem list
    '''val''' sortuj : elem list -> elem list   (* Procedura sortująca listy elementów. *)
&nbsp;&nbsp;'''end''';;
  '''end''';;
     
 
Jeden z algorytmów sortowania, to heap-sort.
<p align="justify"> 
Jeden z możliwych algorytmów sortowania to heap-sort.
Możemy go zapisać w postaci funktora sparametryzowanego implementacją kolejek priorytetowych.
</p>
        
        
  '''module''' HeapSort (PrQ : PRI_QUEUE_FUNC): SORTING =  
  '''module''' HeapSort (PrQ : PRI_QUEUE_FUNC): SORTING =  
&nbsp;&nbsp;'''functor''' (Elem : PORZADEK_LINIOWY) ->  
  '''functor''' (Elem : PORZADEK_LINIOWY) ->  
&nbsp;&nbsp;&nbsp;&nbsp;'''struct'''
    '''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''type''' elem = Elem.t
      '''type''' elem = Elem.t
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''module''' PQ = PrQ (Elem)
      '''module''' PQ = PrQ (Elem)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''open''' PQ
      '''open''' PQ
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''let''' '''rec''' wkladaj l =
      '''let rec''' wkladaj l =
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fold_left (fun h x -> put x h) empty l
        List.fold_left (fun h x -> put x h) empty l
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''let''' '''rec''' wyjmuj q a =  
      '''let''' '''rec''' wyjmuj q a =  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''if''' q = empty '''then'''  
        '''if''' q = empty '''then'''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List.rev a  
          List.rev a  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''else'''
        '''else'''
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wyjmuj (removemin q) (min q :: a)
          wyjmuj (removemin q) (min q :: a)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''let''' sortuj l = wyjmuj (wkladaj l empty) []
      '''let''' sortuj l = wyjmuj (wkladaj l) []
&nbsp;&nbsp;&nbsp;&nbsp;'''end''';;
    '''end''';;
&nbsp;&nbsp;
 
  '''module''' Sortowanie = HeapSort (PriQueue);;
  '''module''' Sortowanie = HeapSort (PriQueue);;
&nbsp;&nbsp;
 
  '''module''' S = Sortowanie (IntOrder);;
  '''module''' S = Sortowanie (IntOrder);;
&nbsp;&nbsp;
 
  S.sortuj [1;7;3;6;5;2;4;8];;
  S.sortuj [1;7;3;6;5;2;4;8];;
     
''- : S.elem list = [1; 2; 3; 4; 5; 6; 7; 8]''
Jak widać z powyższego przykładu, jeżeli będziemy zapisywać moduły w postaci funktorów, to będziemy z nich tworzyć łatwo wymienne
 
elementy.
Możliwy jest również taki zapis:
 
'''module''' Sort = HeapSort (PriQueue) (IntOrder);;
 
Sort.sortuj [1;7;3;6;5;2;4;8];;
''- : Sort.elem list = [1; 2; 3; 4; 5; 6; 7; 8]''


==Funktory wyższych rzędów==
==Funktory wyższych rzędów==
<p align="justify">
Tak jak istnieją procedury wyższych rzędów, istnieją również funktory wyższych rzędów.
Są to funktory, których parametrami i/lub wynikiem są funktory.
Oto przykład, stanowiący kontynuację przykładu z poprzedniego punktu:
</p>


Tak jak istnieją procedury wyższych rzędów, istnieją również funktory wyższych rzędów. Są to funktory, których parametrami i/lub wynikiem są funktory. Oto przykład, stanowiący kontynuację poprzedniego przykładu:
{{przyklad|[Mediana]||}}
 
<p align="justify">
Przykład:
Jeden ze sposobów wyznaczania mediany ciągu (nie najbardziej efektywny, ale za to prosty) polega na posortowaniu ciągu i wybraniu elementu środkowego.  
Opierając się na sortowaniu możemy wprowadzić obliczanie mediany. Potrzebujemy do tego porządku liniowego i jakiegoś algorytmu
Taki algorytm możemy zapisać w postaci funktora sparametryzowanego algorytmem sortowania i porządkiem liniowym. (Zauważmy, że sortowanie jest tu również funktorem.)
sortowania.
</p>
          
          
  '''module''' '''type''' MEDIANA =  
  '''module type''' MEDIANA =  
&nbsp;&nbsp;functor (Elem : PORZADEK_LINIOWY) ->  
  functor (Elem : PORZADEK_LINIOWY) ->  
&nbsp;&nbsp;&nbsp;&nbsp;'''sig'''
    '''sig'''
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''type''' elem = Elem.t
      '''type''' elem = Elem.t
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''exception''' PustaLista
      '''exception''' PustaLista
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''val''' mediana : elem list -> elem
      '''val''' mediana : elem list -> elem
&nbsp;&nbsp;&nbsp;&nbsp;'''end''';;
    '''end''';;
&nbsp;&nbsp;
 
  '''module''' Mediana : '''functor''' (Sort : SORTING) -> MEDIANA =  
  '''module''' Mediana : '''functor''' (Sort : SORTING) -> MEDIANA =  
&nbsp;&nbsp;'''functor''' (Sort : SORTING) ->  
  '''functor''' (Sort : SORTING) ->  
&nbsp;&nbsp;&nbsp;&nbsp;'''functor''' (Elem : PORZADEK_LINIOWY) ->  
    '''functor''' (Elem : PORZADEK_LINIOWY) ->  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''struct'''
      '''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''type''' elem = Elem.t
        '''type''' elem = Elem.t
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''exception''' PustaLista
        '''exception''' PustaLista
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''module''' S = Sort (Elem)
        '''module''' S = Sort (Elem)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''let''' mediana l =  
        '''let''' mediana l =  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''let''' s = S.sortuj l
          '''let''' s = S.sortuj l
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''and''' n = List.length l
          '''and''' n = List.length l
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''in'''  
          '''in'''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''if''' n = 0 '''then'''  
            '''if''' n = 0 '''then'''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''raise''' PustaLista  
              '''raise''' PustaLista  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''else'''  
            '''else'''  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List.nth s (n / 2)
              List.nth s (n / 2)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''end''';;
      '''end''';;
&nbsp;&nbsp;
 
  '''module''' M = Mediana (HeapSort (PriQueue)) (IntOrder);;
  '''module''' M = Mediana (HeapSort (PriQueue)) (IntOrder);;
&nbsp;&nbsp;
 
  M.mediana [4;3;5;6;2;1;7];;
  M.mediana [4;3;5;6;2;1;7];;
       
Przykład:
Pomysł: Przestrzenie metryczne i grona przestrzeni.Funktory sklejające przestrzenie w grona oraz
funktory określające sposoby łączenia gron w złożone przestrzenie metryczne.
Uzupełnić.
==Morfizmy sygnatur i redukty==


[Pominąć]
<p align="justify">
 
Jak widać z powyższego przykładu, jeżeli będziemy zapisywać moduły w postaci funktorów,  
Morfizmy sygnatur, a w zasadzie związane z nimi kowariantnie redukty, możemy zapisać w postaci funktorów. Załóżmy, że mamy dane trzy sygnatury:
to będziemy z nich tworzyć łatwo wymienne elementy.
     
Tworząc funktory wyższych rzędów, zwiększamy naszą elastyczność i możemy łączyć rozmaite algorytmy i struktury danych w praktycznie dowolne kombinacje.
'''module''' '''type''' S1 =
</p>
&nbsp;&nbsp;'''sig'''
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t1
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t2
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' a : t1
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' b : t2
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' f : t1 -> t2
&nbsp;&nbsp;'''end''';;
&nbsp;&nbsp;
module '''type''' S2 =
&nbsp;&nbsp;'''sig'''
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' s1
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' s2
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' x : s1
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' y : s2
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' p : s1 -> s2
&nbsp;&nbsp;'''end''';;
&nbsp;&nbsp;
'''module''' '''type''' S =
&nbsp;&nbsp;'''sig'''
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' v : t
&nbsp;&nbsp;&nbsp;&nbsp;'''val''' m : t -> t
&nbsp;&nbsp;'''end''';;
     
Możemy sobie wyobrazić trywialne morfizmy sygnatur <math> \sigma : S_1 \to S</math> i <math> \sigma' : S_2 \to S</math> oraz redukty
<math> \_{}_{|\sigma}:S \to S1</math> i <math> \_{}_{|\sigma'}:S \to S2</math>.
     
W pierwszym podejściu możemy zrobić to tak:
     
'''module''' '''type''' M1 = '''functor''' (Sig : S) -> S1;;
'''module''' '''type''' M2 = '''functor''' (Sig : S) -> S2;;
&nbsp;&nbsp;
'''module''' F1 : M1 = '''functor''' (Mod : S) ->
&nbsp;&nbsp;'''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''open''' Mod
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t1 = t
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t2 = t
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' a = v
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' b = v
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' f = m
&nbsp;&nbsp;'''end''';;
&nbsp;&nbsp;
'''module''' F2 : M2 = '''functor''' (Mod : S) ->
&nbsp;&nbsp;'''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''open''' Mod
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' s1 = t
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' s2 = t
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' x = v
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' y = v
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' p = m
&nbsp;&nbsp;'''end''';;
     
Wówczas jednak tracimy część informacji. W szczególności, jeżeli złączymy dwa moduły będące wynikami zastosowania
powyższych funktorów do tej samej struktury, to stracimy informację o tym, jakie składowe są sobie równe.
     
'''module''' Suma = '''functor''' (M : S) ->
&nbsp;&nbsp;'''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''include''' F1(M)
&nbsp;&nbsp;&nbsp;&nbsp;'''include''' F2(M)
&nbsp;&nbsp;'''end''';;
&nbsp;&nbsp;
'''module''' Test =
&nbsp;&nbsp;Suma('''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t = int
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' v = 42
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' m i = i
&nbsp;&nbsp;'''end''');;
&nbsp;&nbsp;
Test.a = Test.x;;
''This expression has type Test.s1 but is here used with '''type''' Test.t1''     
 
Uzyskaliśmy sumę rozłączną, w której te same elementy, ale pochodzące
z różnych funktorów są całkowicie od siebie oddzielone.
 
Spróbujmy trochę inaczej zapisać powyższe funktory, tak, aby te same składowe były sobie równe. Dodatkowo, niech każdy funktor wprowadza też coś nowego.
     
'''module''' F1 = '''functor''' (Mod : S) ->
&nbsp;&nbsp;'''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''open''' Mod
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t1 = t
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t2 = t
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' a = v
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' b = v
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' f = m
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' nowe1 = "42"
&nbsp;&nbsp;'''end''';;
&nbsp;&nbsp;
'''module''' F2 = '''functor''' (Mod : S) ->
&nbsp;&nbsp;'''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''open''' Mod
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' s1 = t
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' s2 = t
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' x = v
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' y = v
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' p = m
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' nowe2 = 42.0
&nbsp;&nbsp;'''end''';;
     
Teraz złączenie wyników obydwu funktorów nie będzie sumą rozłączną, ale "pushoutem".     


'''module''' Pushout = '''functor''' (M : S) ->
&nbsp;&nbsp;'''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''include''' F1(M)
&nbsp;&nbsp;&nbsp;&nbsp;'''include''' F2(M)
&nbsp;&nbsp;'''end''';;
&nbsp;&nbsp;
'''module''' Test =
&nbsp;&nbsp;Pushout('''struct'''
&nbsp;&nbsp;&nbsp;&nbsp;'''type''' t = int
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' v = 42
&nbsp;&nbsp;&nbsp;&nbsp;'''let''' m i = i
&nbsp;&nbsp;'''end''');;
&nbsp;&nbsp;
Test.a = Test.x;;
''- : bool = true''     


Zastanówmy się chwilę o co bogatsza jest sygnatura tak zdefiniowanych
funktorów. Czy sygnatura wynikowa jest bogatsza? W zasadzie nie, bo nie wiadomo nic konkretnego o strukturze wynikowych
typów, czy wartości. Wiadomo natomiast jaki jest związek między argumentem funktora, a strukturą wynikową. Sam funktor jest mniej abstrakcyjny.


== Laboratoria i ćwiczenia ==
== Ćwiczenia ==


[[Programowanie funkcyjne/Funktory/Ćwiczenia        | Laboratoria i ćwiczenia do wykładu]]
[[Programowanie funkcyjne/Funktory/Ćwiczenia        | Ćwiczenia]]

Aktualna wersja na dzień 15:24, 3 gru 2006

Wstęp

Zwykle jedne moduły korzystają z innych modułów. Zgodnie z zasadą ukrywania informacji (ang. information hiding), istotne powinny być jedynie specyfikacje wykorzystywanych modułów, a nie ich konkretne implementacje. Podstawienie dowolnej implementacji o zadanej sygnaturze i spełniającej zadaną specyfikację powinno być akceptowalne z punktu widzenia modułów korzystających z takiej implementacji.

Skoro rozdzieliliśmy pojęcia interfejsu (sygnatury) i implementacji (struktury), to możemy zapisywać moduły korzystające z innych modułów w bardziej abstrakcyjny sposób. Zamiast określać dokładnie z jakiej implementacji korzysta dany moduł, określamy jedynie jej sygnaturę (i przez domniemanie również specyfikację). Otrzymujemy w ten sposób funktory.

Funktory to moduły sparametryzowane. Ich parametrami są struktury o określonych sygnaturach, z których mają one korzystać. Dopiero gdy podstawimy w miejsce parametrów konkretne struktury, otrzymujemy strukturę implementowaną przez funktor.

Zwykłe moduły (struktury) tworzą statyczną sieć powiązań. Natomiast moduły są jak wymienne elementy, klocki, które możemy łączyć na rozmaite sposoby.

Na funktory możemy też patrzeć jak na funkcje na strukturach --- przetwarzają one struktury-parametry w struktury wynikowe. Należy jednak pamiętać, że podstawianie parametrów funktorów dokonuje się w sposób statyczny -- w czasie kompilacji.

Proste funktory

Tak samo jak w przypadku struktur, w przypadku funktorów mamy pojęcie sygnatury. Sygnatura funktora określa sygnaturę parametrów oraz sygnaturę wyniku.

<sygnatura> ::= functor ( <Identyfikator> : <sygnatura> ) -> <sygnatura>
<struktura> ::= functor ( <Identyfikator> : <sygnatura> ) -> <struktura>  |
                <struktura> ( <struktura )

Jeżeli funktor ma więcej niż jeden parametr, to tak jak w przypadku procedur, zapisujemy go jako funktor z pierwszym parametrem, którego wynikiem jest funktor z drugim parametrem itd. Z powyższej składni widać też, że funktory mogą być nie tylko wynikiem, ale i parametrami funktorów, czyli mamy funktory wyższych rzędów. Zajmiemy się nimi dalej.

Przykład [Kolejka priorytetowa]

Przypuśćmy, że chcemy zaimplementować kolejkę priorytetową elementów bliżej nieokreślonego typu. Na elementach kolejki musi być określony porządek liniowy, zgodnie z którym będziemy szeregować elementy w kolejce. Kolejkę zaimplementujemy w postaci funktora. Parametrem funktora będzie struktura określająca typ elementów i porządek liniowy na nich.

type porownanie = Mniejsze | Rowne | Wieksze;;

module type PORZADEK_LINIOWY = 
  sig
    type t                                 (* Typ elementów.    *)
    val porownaj : t -> t -> porownanie    (* Porządek liniowy. *)
  end;;
     

Kolejka priorytetowa powinna mieć sygnaturę postaci:

module type PRI_QUEUE_FUNC = functor (Elem : PORZADEK_LINIOWY) -> 
  sig
    exception EmptyQueue
    type elem = Elem.t                (* Typ elementów.                    *)
    type queue                        (* Typ kolejki.                      *)
    val empty : queue                 (* Pusta kolejka.                    *)
    val put : elem -> queue -> queue  (* Wstawienie do kolejki.            *)
    val min : queue -> elem           (* Element najmniejszy.              *)
    val removemin : queue -> queue    (* Usunięcie elementu najmniejszego. *)
  end;;

Zwróćmy uwagę na to, że typ queue jest abstrakcyjny. Ukrywamy w ten sposób strukturę kolejki. Zwróćmy też uwagę na to, że typ elem jest określony jako Elem.t. Jest to specyficzna konstrukcja. Przy jej zastosowaniu, cokolwiek będzie wiadome o typie Elem.t, będzie również wiadome o typie elem, mimo że w sygnaturze PORZADEK_LINIOWY typ t jest typem abstrakcyjnym. Implementację kolejki priorytetowej możemy zrealizować w następujący sposób:

module PriQueue : PRI_QUEUE_FUNC =
  functor (Elem : PORZADEK_LINIOWY) -> 
  struct
    type elem = Elem.t
    exception EmptyQueue
    type queue = elem list
    let empty = []
    let rec put x q = 
      match q with 
        [] -> [x] |
        h::t -> if Elem.porownaj x h = Mniejsze then 
          x :: q
        else
          h::(put x t)
    let min q =
      match q with 
        [] -> raise EmptyQueue |
        h::_ -> h
    let removemin (q:queue) = 
      match q with 
        [] -> raise EmptyQueue |
        _::t -> t
  end;;

Kolejka priorytetowa liczb całkowitych może być zrealizowana poprzez podstawienie jako argumentu funktora następującej struktury:

module IntOrder = 
  struct
    type t = int
    let porownaj (x:int) (y:int) = 
      if x < y then Mniejsze else
      if x > y then Wieksze else Rowne
  end;;
  
module IntQueue = PriQueue (IntOrder);;

IntQueue.min (IntQueue.put 42 IntQueue.empty);;
- : IntQueue.elem = 42

Zwróćmy uwagę, że nie ograniczamy struktury IntOrder do sygnatury PORZADEK_LINIOWY. Chcemy, aby typ t nie był typem abstrakcyjnym, lecz był równoważny int. Dzięki temu będzie wiadomo, że elementy kolejki są również typu int.

Dla porównania spójrzmy na poniższy przykład. Jest to definicja modułu, którego nie da się używać, gdyż typ elementów jest abstrakcyjny i nie jesteśmy w stanie utworzyć jakiegokolwiek elementu.

module SomeOrder : PORZADEK_LINIOWY = 
  struct
    type t = int
    let porownaj (x:int) (y:int) = 
      if x < y then Mniejsze else
      if x > y then Wieksze else Rowne
  end;;

module SomeQueue = PriQueue (SomeOrder);;

SomeQueue.min (SomeQueue.put 42 SomeQueue.empty);;
This expression has type int but is here used with type SomeQueue.elem = SomeOrder.t


Przykład [Sortowanie]

Innym podstawowym pojęciem związanym z porządkami liniowymi jest sortowanie. Również możemy je zapisać w postaci funktora sparametryzowanego porządkiem liniowym.

module type SORTING = functor (Elem : PORZADEK_LINIOWY) -> 
  sig
    type elem = Elem.t                    (* Typ elementów.                       *)
    val sortuj : elem list -> elem list   (* Procedura sortująca listy elementów. *)
  end;;

Jeden z możliwych algorytmów sortowania to heap-sort. Możemy go zapisać w postaci funktora sparametryzowanego implementacją kolejek priorytetowych.

module HeapSort (PrQ : PRI_QUEUE_FUNC): SORTING = 
  functor (Elem : PORZADEK_LINIOWY) -> 
    struct
      type elem = Elem.t
      module PQ = PrQ (Elem)
      open PQ
      let rec wkladaj l =
        List.fold_left (fun h x -> put x h) empty l
      let rec wyjmuj q a = 
        if q = empty then 
          List.rev a 
        else
          wyjmuj (removemin q) (min q :: a)
      let sortuj l = wyjmuj (wkladaj l) []
    end;;
  
module Sortowanie = HeapSort (PriQueue);;
  
module S = Sortowanie (IntOrder);;
  
S.sortuj [1;7;3;6;5;2;4;8];;
- : S.elem list = [1; 2; 3; 4; 5; 6; 7; 8]

Możliwy jest również taki zapis:

module Sort = HeapSort (PriQueue) (IntOrder);;
 
Sort.sortuj [1;7;3;6;5;2;4;8];;
- : Sort.elem list = [1; 2; 3; 4; 5; 6; 7; 8]

Funktory wyższych rzędów

Tak jak istnieją procedury wyższych rzędów, istnieją również funktory wyższych rzędów. Są to funktory, których parametrami i/lub wynikiem są funktory. Oto przykład, stanowiący kontynuację przykładu z poprzedniego punktu:

Przykład [Mediana]

Jeden ze sposobów wyznaczania mediany ciągu (nie najbardziej efektywny, ale za to prosty) polega na posortowaniu ciągu i wybraniu elementu środkowego. Taki algorytm możemy zapisać w postaci funktora sparametryzowanego algorytmem sortowania i porządkiem liniowym. (Zauważmy, że sortowanie jest tu również funktorem.)

module type MEDIANA = 
  functor (Elem : PORZADEK_LINIOWY) -> 
    sig
      type elem = Elem.t
      exception PustaLista
      val mediana : elem list -> elem
    end;;
  
module Mediana : functor (Sort : SORTING) -> MEDIANA = 
  functor (Sort : SORTING) -> 
    functor (Elem : PORZADEK_LINIOWY) -> 
      struct
        type elem = Elem.t
        exception PustaLista
        module S = Sort (Elem)
        let mediana l = 
          let s = S.sortuj l
          and n = List.length l
          in 
            if n = 0 then 
              raise PustaLista 
            else 
              List.nth s (n / 2)
      end;;
  
module M = Mediana (HeapSort (PriQueue)) (IntOrder);;
  
M.mediana [4;3;5;6;2;1;7];;

Jak widać z powyższego przykładu, jeżeli będziemy zapisywać moduły w postaci funktorów, to będziemy z nich tworzyć łatwo wymienne elementy. Tworząc funktory wyższych rzędów, zwiększamy naszą elastyczność i możemy łączyć rozmaite algorytmy i struktury danych w praktycznie dowolne kombinacje.


Ćwiczenia

Ćwiczenia