Programowanie funkcyjne/System typów: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Przemek (dyskusja | edycje)
Przemek (dyskusja | edycje)
Linia 89: Linia 89:
</math></center><br/>
</math></center><br/>
        
        
{{przyklad|||}}}
{{przyklad|||}}
Pokazać, że <tt>let id = (fun x -> x) '''in''' id id</tt> jest typu <math>\alpha \to \alpha</math>.
Pokazać, że <tt>let id = (fun x -> x) '''in''' id id</tt> jest typu <math>\alpha \to \alpha</math>.


Linia 95: Linia 95:
aplikacji funkcji.  
aplikacji funkcji.  


{{przyklad|||}}}
{{przyklad|||}}
Pokazać, że nie da się otypować <tt>(function id -> id id)(function x -> x)</tt>.\end{przyklad}
Pokazać, że nie da się otypować <tt>(function id -> id id)(function x -> x)</tt>.\end{przyklad}



Wersja z 06:38, 20 lip 2006

UNDER CONSTRUCTION

Składnia drobnego fragmentu Ocamla

<wyrażenie>    ::= <identyfikator> | <konstruktor> | 0 | 1 | ... 
                   if <wyrażenie> then <wyrażenie> else <wyrażenie> | 
                   <wyrażenie> <wyrażenie> |
                   function <identfikator>  <wyrażenie> |
                   let <identyfikator> = <wyrażenie> in <wyrażenie> |
                   (<wyrażenie>)
<konstruktor> ::=  <Identyfikator> { <wyrażenie> }*      

Niektóre konstruktory i stałe mają specjalną składnię, np. konstruktor n-tki i operacje arytmetyczne. Gdy tylko nie będzie to budzić niejednoznaczności, a będzie to przydatne, będziemy traktowali konstruktory również jako nazwy procedur konstruujących wartości.

Typy i środowiska typujące

Przez typy proste bedziemy rozumieli napisy generowane przez następującą gramatykę:

<typ prosty>        ::= <zmienna typowa> | <typ prosty>  <typ prosty> | 
                        <typ prosty>1 <typ prosty>n  <konstruktor typu>n
<konstruktor typu>  ::= int | bool | list | ...*...*... | ...
<zmienna typowa>    ::= \alpha | \beta | \gamma | ...
     

W momencie gdy zechcemy przypisywać typy obiektom polimorficznym, będą nam potrzebne schematy typów:

<schemat typu> ::= α1,,αn.<typ prosty> |
                   <typ prosty>
     

Schemat typu α1,,αn.τ możemy rozumieć jako opis zbioru wszystkich typów, jakie możemy uzyskać podstawiając w τ za α1,,αn dowolne typy. Środowisko typujące, to skończona funkcja (mapa) przypisująca identyfikatorom typy (proste, lub schematy, w zależności od potrzeby). Zakładamy istnienie takiego środowiska typującego wszystkie stałe i konstruktory wbudowane w język. Oczywiście typy tych stałych i konstruktorów muszą być zgodne z ich arnościami. Niech A oznacza środowisko typujące. Przez A,x:τ będziemy oznaczać środowisko identyczne z A, z tym wyjątkiem, że nazwie x przyporządkowuje typ τ. Typowanie wyrażeń w programie będziemy oznaczali przez stwierdzenia postaci: Ae:τ, co oznacza, że zakładając typowania ze środowiska A możemy stwierdzić, że e jest typu τ.

Reguły typowania dla prostych typów

Bez polimorfizmu. Tylko monomorfizm. Let jest traktowany wyłącznie jak lukier syntaktyczny. Środowisko typujące używa wyłącznie typów prostych.

xdom(A)Ax:A(x)


Ae1:τ1Aen:τnA(e1,,en):τ1**τn


A():unit


Aw:boolAe1:τAe2:τAif w then e1 else e2:τ


Af:τ1τ2Ae:τ1Af e:τ2


A,x:τ1e:τ2A(function xe):τ1τ2


Ae1:τ1A,x:τ1e2:τ2Alet x=e1 in e2:τ2


Ae:τA(e):τ



Przykład

Pokazać, że:

let id = (function x -> x) in id 42 : int.

Przykład

Pokazać, że:

let id = (fun x -> x) in id id 

nie da się otypować (brak polimorfizmu).

Polimorfizm

Chcąc wyrazić, że jakieś wyrażenie (np. id w powyższym przykładzie) może przyjmować różne typy w różnych wystąpieniach, musimy użyć schematów typów. Taki schemat powinien opisywać wszystkie możliwe typy wartości polimorficznej.

Fakt

Dla każdego wyrażenia istnieje (najbardziej ogólny) schemat typu, który obejmuje wszystkie typy, które można wyprowadzić dla tego wyrażenia.

Równocześnie powinniśmy być w stanie ukonkretniać ten typ na różne sposoby.

xdom(A),A(x)=α.τAx:τ[π/α]


Ae1:τ1A,x:Free(τ1)Free(A).τ1e2:τ2Alet x=e1 in e2:τ2


Przykład

Pokazać, że let id = (fun x -> x) in id id jest typu αα.

Zauważmy, że mamy polimorficzną regułę dla let-a, ale nie mamy polimorficznej reguły dla λ-abstrakcji i aplikacji funkcji.

Przykład

Pokazać, że nie da się otypować (function id -> id id)(function x -> x).\end{przyklad}

Rekurencja

Jak możemy wnioskować o typie procedur rekurencyjnych? Sięgnijmy po technikę podobną do prezentowanej wcześniej. Definicję rekurencyjną możemy wyrazić jako punkt stały. Załóżmy, że środowisko typujące zawiera operator punktu stałego: \[ A(\mathtt{fix}) = \forall_\alpha. (\alpha \to \alpha) \to \alpha \] Definicję rekurencyjną postaci: \[ \mathtt{let\ rec}\ f\ x = e_1\ \mathtt{in}\ e_2 \] zamieniamy na: \[ \mathtt{let}\ f = \mathtt{fix}(\mathtt{function}\ f \to \mathtt{function}\ x \to e_1) \ \mathtt{in}\ e_2 \] Zwróćmy uwagę, że wewnątrz definicji f funkcja ta niejest polimorficzna.

Wyjątki

\[ \frac{A \vdash e : \mathtt{exn}} {A \vdash \mathtt{raise}\ e : \tau} \] Uproszczona wersja --- nie wnikamy we wzorce. \[ \frac{A \vdash e_1:\tau    A,x:\mathtt{exn} \vdash e_2:\tau} {A \vdash \mathtt{try}\ e_1\ \mathtt{with}\ x \to e_2 : \tau} \]

Konstrukcje imperatywne i słabe zmienne typowe

Dla sekwencji instrukcji imperatywnych mamy regułę: \[ \frac{A \vdash e_1 : \tau_1    A \vdash e_2 : \tau} {A \vdash e_1;e_2 : \tau} \] Gdyz tak naprawdę ; możemy zdefiniować jako: let (;) x y = y;;

Gdzie jest problem?

let r = ref (function x -> x);;

Powinniśmy być w stanie pokazać, że r:α.(αα) ref. Jeśli się zastanowimy jakiego typu są \texttt{:=} i !, to jedyne sensowne typy, to: \[ A \vdash \mathtt{:=} : \forall_\alpha.\alpha\ \mathtt{ref}\ \to \alpha \to\ \mathtt{unit} \] \[ A \vdash \mathtt{!} : \forall_\alpha.\alpha\ \mathtt{ref}\ \to \alpha \] Czyli, np.: \[ A, \mathtt{r}: \forall_\alpha.(\alpha \to \alpha)\ \mathtt{ref} \vdash \mathtt{(r := (function\ x \to x + 42))}:\mathtt{unit} \] \[ A, \mathtt{r}: \forall_\alpha.(\alpha \to \alpha)\ \mathtt{ref} \vdash \mathtt{!r}\ \mathtt{true} : \mathtt{bool} \] To jednak nie ma sensu!

Rozwiązaniem są tzw.\ słabe zmienne typowe, oznaczane jako _α:

    • Słabe zmienne typowe nie są kwantyfikowane.
      Za słabe zmienne typowe (w trakcie rekonstrukcji typów)
    można podstawić typy proste
    zawierające tylko słabe zmienne typowe.
    • Każda próba podstawienia za słabą zmienną typową jakiegoś
    typu powoduje, że na stałe zostaje ona zastąpiona tym typem.
    (Top level.)
    • Uwaga
      Kompilator może nazywać różne słabe zmienne typowe
    tak samo.
    Trzeba sobie zdawać sprawę kiedy dwie zmienne typowe są
    różne.
    • Przy deklaracji let, typ stałej jest kwantyfikowany*;tylko wtedy, gdy wyrażenie definiujące stałą nie jest
    ekspansywne.**;Referencje i aplikacje są ekspansywne.

let r = ref (function x -> x);; val r : ('_a -> '_a) ref = {contents = <fun>}r := (function x -> 42);; r : (int -> int) ref   let id = (function x -> x);; val id : 'a -> 'a = <fun>let idd = id id;; val idd : '_a -> '_a = <fun>let idd x = id id x;; val idd : 'a -> 'a = <fun>   let p = id id;; val p : '_a -> '_alet r = ref p;; val r : ('_a -> '_a) refp 42;; p: int -> int'r: (int -> int) ref   let r = ref id;; val r : ('_a -> '_a) reflet q = ref id;; val q : ('_a -> '_a) ref!r 42 r: (int -> int) ref'val q : ('_a -> '_a) ref   let r = ref id;; val r : ('_a -> '_a) reflet q = ref (!r);; val q : ('_a -> '_a) ref!r 42;; r: (int -> int) ref'q: (int -> int) ref   let f x =   let r = ref id   in (r := compose !r !r; !r x);; val f : 'a -> 'af 42;; = 42f true;; = true   let g =   let r = ref id   in (r := compose !r !r; !r);; val g : '_a -> '_ag 42;; = 42'g : int -> int   let f x = let r = ref x in (function y -> r := compose !r !r; !r y);;


[.....]

let f x = let r = ref x in (function y -> r := y; !r);; val f : 'a -> 'a -> 'a

Jeśli zdefiniujemy procedurę, której typ zawiera słabe zmienne typowe, a chcemy, żeby miała silne, to możemy zastosować η-ekspansję, tzn.\ dodać formalny argument procedury. Jeżeli nie ma (imperatywnych) przeciwwskazań, to uzyskamy procedurę równoważną.

Laboratoria i ćwiczenia

Laboratoria i ćwiczenia do wykładu