Programowanie funkcyjne/System typów: Różnice pomiędzy wersjami
| Linia 165: | Linia 165: | ||
To jednak nie ma sensu! | To jednak nie ma sensu! | ||
Rozwiązaniem są tzw. | Rozwiązaniem są tzw. słabe zmienne typowe, oznaczane jako <math>\_\alpha</math>: | ||
* Słabe zmienne typowe nie są kwantyfikowane. | * 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. | * Za słabe zmienne typowe (w trakcie rekonstrukcji typów) można podstawić typy proste zawierające tylko słabe zmienne typowe. | ||
Wersja z 06:59, 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ą.