Programowanie funkcyjne/System typów: Różnice pomiędzy wersjami
Linia 128: | Linia 128: | ||
==Konstrukcje imperatywne i słabe zmienne typowe== | ==Konstrukcje imperatywne i słabe zmienne typowe== | ||
Dla sekwencji instrukcji imperatywnych mamy regułę: | Dla sekwencji instrukcji imperatywnych mamy regułę: | ||
<center><math> | |||
\frac{A \vdash e_1 : \tau_1 | \frac{A \vdash e_1 : \tau_1 A \vdash e_2 : \tau} | ||
{A \vdash e_1;e_2 : \tau} | {A \vdash e_1;e_2 : \tau} | ||
</math></center><br/> | |||
Gdyz tak naprawdę <tt>;</tt> możemy zdefiniować jako: | Gdyz tak naprawdę <tt>;</tt> możemy zdefiniować jako: | ||
'''let''' (;) x y = y;; | |||
'''let''' (;) x y = y;; | |||
Gdzie jest problem? | Gdzie jest problem? | ||
'''let''' r = ref (function x -> x);; | |||
Powinniśmy być w stanie pokazać, że: | |||
Powinniśmy być w stanie pokazać, że | <math>\texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref}</math>. | ||
<math>\ | |||
Jeśli się zastanowimy jakiego typu są | Jeśli się zastanowimy jakiego typu są <tt>:=</tt> i <tt>!</tt>, to jedyne sensowne typy, to: | ||
A \vdash \ | <center><math> | ||
\forall_\alpha.\alpha\ \ | A \vdash \texttt{:=} : | ||
\forall_\alpha.\alpha\ \texttt{ref}\ \to \alpha \to\ \texttt{unit} | |||
</math></center><br/> | |||
A \vdash \ | |||
<center><math> | |||
A \vdash \texttt{!} : \forall_\alpha.\alpha\ \texttt{ref}\ \to \alpha | |||
</math></center><br/> | |||
Czyli, np.: | Czyli, np.: | ||
A, \ | <center><math> | ||
\vdash \ | A, \texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref} | ||
\vdash \texttt{(r := (function\ x \to x + 42))}:\texttt{unit} | |||
</math></center><br/> | |||
A, \ | <center><math> | ||
\vdash \ | A, \texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref} | ||
\vdash \texttt{!r}\ \texttt{true} : \texttt{bool} | |||
</math></center><br/> | |||
To jednak nie ma sensu! | To jednak nie ma sensu! | ||
Rozwiązaniem są tzw.\ słabe zmienne typowe, oznaczane jako <math>\_\alpha</math>: | Rozwiązaniem są tzw.\ słabe zmienne typowe, oznaczane jako <math>\_\alpha</math>: | ||
* | * 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 <tt>let</tt>, 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);; | '''let''' r = ref ('''function''' x -> x);; | ||
''val r : ('_a -> '_a) ref = {contents = <fun>}''r := (function x -> 42);; | '''''val''' r : ('_a -> '_a) '''ref''' = {contents = <fun>}''r := ('''function''' x -> 42);; | ||
''r : (int -> int) ref'' | ''r : (int -> int) '''ref''''' | ||
'''let''' id = (function x -> x);; | | ||
''val id : 'a -> 'a = <fun>'''''let''' idd = id id;; | '''let''' id = ('''function''' x -> x);; | ||
''val idd : '_a -> '_a = <fun>'''''let''' idd x = id id x;; | '''''val''' id : 'a -> 'a = <fun>'' | ||
''val idd : 'a -> 'a = <fun>'' | '''let''' idd = id id;; | ||
'''let''' p = id id;; | '''''val''' idd : '_a -> '_a = <fun>'' | ||
''val p : '_a -> '_a'''''let''' r = ref p;; | '''let''' idd x = id id x;; | ||
''val r : ('_a -> '_a) ref''p 42;; | '''''val''' idd : 'a -> 'a = <fun>'' | ||
''p: int -> int''''r: (int -> int) ref'' | | ||
'''let''' r = ref id;; | '''let''' p = id id;; | ||
''val r : ('_a -> '_a) ref'''''let''' q = ref id;; | '''''val''' p : '_a -> '_a'' | ||
''val q : ('_a -> '_a) ref''!r 42 | '''let''' r = '''ref''' p;; | ||
''r: (int -> int) ref''''val q : ('_a -> '_a) ref'' | '''''val''' r : ('_a -> '_a) '''ref''''' | ||
'''let''' r = ref id;; | p 42;; | ||
''val r : ('_a -> '_a) ref'''''let''' q = ref (!r);; | ''p: int -> int''''r: (int -> int) '''ref''''' | ||
''val q : ('_a -> '_a) ref''!r 42;; | | ||
''r: (int -> int) ref''''q: (int -> int) ref'' | '''let''' r = '''ref''' id;; | ||
'''let''' f x = | '''''val''' r : ('_a -> '_a) '''ref''''' | ||
let r = ref id | '''let''' q = '''ref''' id;; | ||
in (r := compose !r !r; !r x);; | '''''val''' q : ('_a -> '_a) '''ref''''' | ||
''val f : 'a -> 'a''f 42;; ''= 42''f true;; ''= true'' | !r 42 | ||
'''let''' g = | ''r: (int -> int) '''ref''''' | ||
let r = ref id | '''''val''' q : ('_a -> '_a) '''ref''''' | ||
in (r := compose !r !r; !r);; | | ||
''val g : '_a -> '_a''g 42;; ''= 42''''g : int -> int'' | '''let''' r = '''ref''' id;; | ||
'''let''' f x = | '''''val''' r : ('_a -> '_a) '''ref''''' | ||
'''let''' r = ref x | '''let''' q = '''ref''' (!r);; | ||
'''in''' (function y -> r := compose !r !r; !r y);; | '''''val''' q : ('_a -> '_a) '''ref''''' | ||
!r 42;; | |||
''r: (int -> int) '''ref''''' | |||
[.....] | ''q: (int -> int) '''ref''''' | ||
| |||
let f x = let r = ref x '''in''' (function y -> r := y; !r);; | '''let''' f x = | ||
''val f : 'a -> 'a -> 'a'' | '''let''' r = '''ref''' id | ||
'''in''' (r := compose !r !r; !r x);; | |||
'''''val''' f : 'a -> 'a'' | |||
f 42;; ''= 42'' | |||
f true;; ''= true'' | |||
| |||
'''let''' g = | |||
'''let''' r = '''ref''' id | |||
'''in''' (r := compose !r !r; !r);; | |||
'''''val''' g : '_a -> '_a'' | |||
g 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 | Jeśli zdefiniujemy procedurę, której typ zawiera słabe zmienne typowe, a chcemy, żeby miała silne, to możemy zastosować <math>\eta</math>-ekspansję, tzn.\ dodać formalny argument procedury. Jeżeli nie ma (imperatywnych) przeciwwskazań, to uzyskamy | ||
typowe, a chcemy, żeby miała silne, to możemy zastosować | |||
<math>\eta</math>-ekspansję, tzn.\ dodać formalny argument procedury. | |||
Jeżeli nie ma (imperatywnych) przeciwwskazań, to uzyskamy | |||
procedurę równoważną. | procedurę równoważną. | ||
Wersja z 06:52, 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 -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> ::= .<typ prosty> | <typ prosty>
Schemat typu możemy rozumieć jako opis zbioru wszystkich typów, jakie możemy uzyskać podstawiając w za 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 oznacza środowisko typujące. Przez będziemy oznaczać środowisko identyczne z , z tym wyjątkiem, że nazwie przyporządkowuje typ . Typowanie wyrażeń w programie będziemy oznaczali przez stwierdzenia postaci: , co oznacza, że zakładając typowania ze środowiska możemy stwierdzić, ż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.
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
Równocześnie powinniśmy być w stanie ukonkretniać ten typ na różne sposoby.
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:
Definicję rekurencyjną postaci:
zamieniamy na:
Zwróćmy uwagę, że wewnątrz definicji f funkcja ta niejest polimorficzna.
Wyjątki
Uproszczona wersja --- nie wnikamy we wzorce.
Konstrukcje imperatywne i słabe zmienne typowe
Dla sekwencji instrukcji imperatywnych mamy regułę:
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:
.
Jeśli się zastanowimy jakiego typu są := i !, to jedyne sensowne typy, to:
Czyli, np.:
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 -> '_a let r = ref p;; val r : ('_a -> '_a) ref p 42;; p: int -> int'r: (int -> int) ref let r = ref id;; val r : ('_a -> '_a) ref let 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) ref let 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 -> 'a f 42;; = 42 f true;; = true let g = let r = ref id in (r := compose !r !r; !r);; val g : '_a -> '_a g 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ą.