Programowanie funkcyjne/System typów: Różnice pomiędzy wersjami
Nie podano opisu zmian |
Nie podano opisu zmian |
||
Linia 4: | Linia 4: | ||
<u>if</u> <wyrażenie> <u>then</u> <wyrażenie> <u>else</u> <wyrażenie> | | <u>if</u> <wyrażenie> <u>then</u> <wyrażenie> <u>else</u> <wyrażenie> | | ||
<wyrażenie> <wyrażenie> | | <wyrażenie> <wyrażenie> | | ||
<u>function</u> <identfikator> <u><math>\to</math></u> <wyrażenie> | | <u>function</u> <identfikator> <u><math> \to</math></u> <wyrażenie> | | ||
<u>let</u> <identyfikator> <u>=</u> <wyrażenie> <u>in</u> <wyrażenie> | | <u>let</u> <identyfikator> <u>=</u> <wyrażenie> <u>in</u> <wyrażenie> | | ||
(<wyrażenie>) | (<wyrażenie>) | ||
<konstruktor> ::= <Identyfikator> { <wyrażenie> }* | <konstruktor> ::= <Identyfikator> { <wyrażenie> }* | ||
Niektóre konstruktory i stałe mają specjalną składnię, np. konstruktor <math>n</math>-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. | Niektóre konstruktory i stałe mają specjalną składnię, np. konstruktor <math> n</math>-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== | ==Typy i środowiska typujące== | ||
Linia 15: | Linia 15: | ||
Przez typy proste bedziemy rozumieli napisy generowane przez następującą gramatykę: | Przez typy proste bedziemy rozumieli napisy generowane przez następującą gramatykę: | ||
<typ prosty> ::= <zmienna typowa> | <typ prosty> <math>\to</math> <typ prosty> | | <typ prosty> ::= <zmienna typowa> | <typ prosty> <math> \to</math> <typ prosty> | | ||
<typ prosty><sub>1</sub> <typ prosty><sub>n</sub> <konstruktor typu><sub>n</sub> | <typ prosty><sub>1</sub> <typ prosty><sub>n</sub> <konstruktor typu><sub>n</sub> | ||
<konstruktor typu> ::= <u>int</u> | <u>bool</u> | <u>list</u> | ...*...*... | ... | <konstruktor typu> ::= <u>int</u> | <u>bool</u> | <u>list</u> | ...*...*... | ... | ||
Linia 22: | Linia 22: | ||
W momencie gdy zechcemy przypisywać typy obiektom polimorficznym, będą nam potrzebne ''schematy typów'': | W momencie gdy zechcemy przypisywać typy obiektom polimorficznym, będą nam potrzebne ''schematy typów'': | ||
<schemat typu> ::= <math>\forall_{\alpha_1, \dots, \alpha_n}</math>.<typ prosty> | | <schemat typu> ::= <math> \forall_{\alpha_1, \dots, \alpha_n}</math>.<typ prosty> | | ||
<typ prosty> | <typ prosty> | ||
Schemat typu <math> \forall_{\alpha_1, \dots, \alpha_n}.\tau</math> możemy rozumieć jako opis zbioru wszystkich typów, jakie możemy uzyskać podstawiając w <math>\tau</math> za <math>\alpha_1, \dots, \alpha_n</math> 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 <math>A</math> oznacza środowisko typujące. Przez <math>A, x:\tau</math> będziemy oznaczać środowisko identyczne z <math>A</math>, z tym wyjątkiem, że nazwie <math>x</math> przyporządkowuje typ <math>\tau</math>. Typowanie wyrażeń w programie będziemy oznaczali przez stwierdzenia postaci: <math>A \vdash e : \tau</math>, co oznacza, | Schemat typu <math> \forall_{\alpha_1, \dots, \alpha_n}.\tau</math> możemy rozumieć jako opis zbioru wszystkich typów, jakie możemy uzyskać podstawiając w <math> \tau</math> za <math> \alpha_1, \dots, \alpha_n</math> 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 <math> A</math> oznacza środowisko typujące. Przez <math> A, x:\tau</math> będziemy oznaczać środowisko identyczne z <math> A</math>, z tym wyjątkiem, że nazwie <math> x</math> przyporządkowuje typ <math> \tau</math>. Typowanie wyrażeń w programie będziemy oznaczali przez stwierdzenia postaci: <math> A \vdash e : \tau</math>, co oznacza, | ||
że zakładając typowania ze środowiska <math>A</math> możemy stwierdzić, że <math>e</math> jest typu <math>\tau</math>. | że zakładając typowania ze środowiska <math> A</math> możemy stwierdzić, że <math> e</math> jest typu <math> \tau</math>. | ||
==Reguły typowania dla prostych typów== | ==Reguły typowania dla prostych typów== | ||
Linia 32: | Linia 32: | ||
Tylko monomorfizm. | Tylko monomorfizm. | ||
<tt>Let</tt> jest traktowany wyłącznie jak lukier syntaktyczny. Środowisko typujące używa wyłącznie typów prostych. | <tt>Let</tt> jest traktowany wyłącznie jak lukier syntaktyczny. Środowisko typujące używa wyłącznie typów prostych. | ||
<center><math>\frac{x \in dom(A)}{A \vdash x : A(x)}</math></center><br/> | <center><math> \frac{x \in dom(A)}{A \vdash x : A(x)}</math></center><br/> | ||
<center><math> | <center><math> | ||
\frac{A \vdash e_1:\tau_1 \dots A\vdash e_n:\tau_n} | \frac{A \vdash e_1:\tau_1 \dots A\vdash e_n:\tau_n} | ||
{A \vdash (e_1, \dots, e_n) : \tau_1 * \cdots * \tau_n} | {A \vdash (e_1, \dots, e_n) : \tau_1 * \cdots * \tau_n} | ||
</math></center><br/> | </math></center><br/> | ||
<center><math> | <center><math> | ||
\frac{}{A \vdash () : unit} | \frac{}{A \vdash () : unit} | ||
</math></center><br/> | </math></center><br/> | ||
<center><math> | <center><math> | ||
\frac{A \vdash w:\texttt{bool} A\vdash e_1:\tau A\vdash e_2:\tau}{A\vdash \texttt{if}\ w\ \texttt{then}\ e_1\ \texttt{else}\ e_2 : \tau} | \frac{A \vdash w:\texttt{bool} A\vdash e_1:\tau A\vdash e_2:\tau}{A\vdash \texttt{if}\ w\ \texttt{then}\ e_1\ \texttt{else}\ e_2 : \tau} | ||
</math></center><br/> | </math></center><br/> | ||
<center><math> | <center><math> | ||
\frac{A\vdash f : \tau_1 \to \tau_2 A\vdash e:\tau_1} | \frac{A\vdash f : \tau_1 \to \tau_2 A\vdash e:\tau_1} | ||
{A\vdash f\ e : \tau_2} | {A\vdash f\ e : \tau_2} | ||
</math></center><br/> | </math></center><br/> | ||
<center><math> | <center><math> | ||
\frac{A, x:\tau_1 \vdash e:\tau_2} | \frac{A, x:\tau_1 \vdash e:\tau_2} | ||
{A \vdash (\texttt{function}\ x \to e) : \tau_1 \to \tau_2} | {A \vdash (\texttt{function}\ x \to e) : \tau_1 \to \tau_2} | ||
</math></center><br/> | </math></center><br/> | ||
<center><math> | <center><math> | ||
\frac{A \vdash e_1:\tau_1 A, x:\tau_1 \vdash e_2:\tau_2} | \frac{A \vdash e_1:\tau_1 A, x:\tau_1 \vdash e_2:\tau_2} | ||
{A \vdash \texttt{let}\ x = e_1\ \texttt{in}\ e_2 : \tau_2} | {A \vdash \texttt{let}\ x = e_1\ \texttt{in}\ e_2 : \tau_2} | ||
</math></center><br/> | </math></center><br/> | ||
<center><math> | <center><math> | ||
\frac{A\vdash e:\tau}{A\vdash (e) : \tau} | \frac{A\vdash e:\tau}{A\vdash (e) : \tau} | ||
</math></center><br/> | </math></center><br/> | ||
Linia 80: | Linia 80: | ||
Równocześnie powinniśmy być w stanie ukonkretniać ten typ na różne sposoby. | Równocześnie powinniśmy być w stanie ukonkretniać ten typ na różne sposoby. | ||
<center><math> | <center><math> | ||
\frac{x \in dom(A), A(x) = \forall_{\overline{\alpha}}.\tau}{A \vdash x : \tau[\overline{\pi}/\overline{\alpha}]} | \frac{x \in dom(A), A(x) = \forall_{\overline{\alpha}}.\tau}{A \vdash x : \tau[\overline{\pi}/\overline{\alpha}]} | ||
</math></center><br/> | </math></center><br/> | ||
<center><math> | <center><math> | ||
\frac{A \vdash e_1:\tau_1 A, x:\forall_{Free(\tau_1) \setminus Free(A)}.\tau_1 \vdash e_2:\tau_2}{A \vdash \texttt{let}\ x = e_1\ \texttt{in}\ e_2 : \tau_2} | \frac{A \vdash e_1:\tau_1 A, x:\forall_{Free(\tau_1) \setminus Free(A)}.\tau_1 \vdash e_2:\tau_2}{A \vdash \texttt{let}\ x = e_1\ \texttt{in}\ e_2 : \tau_2} | ||
</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>. | ||
Zauważmy, że mamy polimorficzną regułę dla <tt>let</tt>-a, ale nie mamy polimorficznej reguły dla <math>\lambda</math>-abstrakcji i | Zauważmy, że mamy polimorficzną regułę dla <tt>let</tt>-a, ale nie mamy polimorficznej reguły dla <math> \lambda</math>-abstrakcji i | ||
aplikacji funkcji. | aplikacji funkcji. | ||
Linia 98: | Linia 98: | ||
==Rekurencja== | ==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: | 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: | ||
<center><math> | <center><math> | ||
A(\texttt{fix}) = \forall_\alpha. (\alpha \to \alpha) \to \alpha </math></center><br/> | A(\texttt{fix}) = \forall_\alpha. (\alpha \to \alpha) \to \alpha </math></center><br/> | ||
Definicję rekurencyjną postaci: | Definicję rekurencyjną postaci: | ||
<center><math> \texttt{let\ rec}\ f\ x = e_1\ \texttt{in}\ e_2 </math></center><br/> | <center><math> \texttt{let\ rec}\ f\ x = e_1\ \texttt{in}\ e_2 </math></center><br/> | ||
zamieniamy na: | zamieniamy na: | ||
<center><math> \texttt{let}\ f = \texttt{fix}(\texttt{function}\ f \to \texttt{function}\ x \to e_1)\ \texttt{in}\ e_2 </math></center><br/> | <center><math> \texttt{let}\ f = \texttt{fix}(\texttt{function}\ f \to \texttt{function}\ x \to e_1)\ \texttt{in}\ e_2 </math></center><br/> | ||
Zwróćmy uwagę, że wewnątrz definicji <tt>f</tt> funkcja ta niejest polimorficzna. | Zwróćmy uwagę, że wewnątrz definicji <tt>f</tt> funkcja ta niejest polimorficzna. | ||
==Wyjątki== | ==Wyjątki== | ||
<center><math> | <center><math> | ||
\frac{A \vdash e : \texttt{exn}} | \frac{A \vdash e : \texttt{exn}} | ||
{A \vdash \texttt{raise}\ e : \tau} | {A \vdash \texttt{raise}\ e : \tau} | ||
Linia 119: | Linia 119: | ||
Uproszczona wersja --- nie wnikamy we wzorce. | Uproszczona wersja --- nie wnikamy we wzorce. | ||
<center><math> | <center><math> | ||
\frac{A \vdash e_1:\tau A,x:\texttt{exn} \vdash e_2:\tau} | \frac{A \vdash e_1:\tau A,x:\texttt{exn} \vdash e_2:\tau} | ||
{A \vdash \texttt{try}\ e_1\ \texttt{with}\ x \to e_2 : \tau} | {A \vdash \texttt{try}\ e_1\ \texttt{with}\ x \to e_2 : \tau} | ||
Linia 126: | Linia 126: | ||
==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> | <center><math> | ||
\frac{A \vdash e_1 : \tau_1 A \vdash e_2 : \tau} | \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} | ||
Linia 141: | Linia 141: | ||
Powinniśmy być w stanie pokazać, że: | Powinniśmy być w stanie pokazać, że: | ||
<center><math>\texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref}</math></center><br/> | <center><math> \texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref}</math></center><br/> | ||
Jeśli się zastanowimy jakiego typu są <tt>:=</tt> i <tt>!</tt>, to jedyne sensowne typy, to: | Jeśli się zastanowimy jakiego typu są <tt>:=</tt> i <tt>!</tt>, to jedyne sensowne typy, to: | ||
<center><math> | <center><math> | ||
A \vdash \texttt{:=} : | A \vdash \texttt{:=} : | ||
\forall_\alpha.\alpha\ \texttt{ref}\ \to \alpha \to\ \texttt{unit} | \forall_\alpha.\alpha\ \texttt{ref}\ \to \alpha \to\ \texttt{unit} | ||
</math></center><br/> | </math></center><br/> | ||
<center><math> | <center><math> | ||
A \vdash \texttt{!} : \forall_\alpha.\alpha\ \texttt{ref}\ \to \alpha | A \vdash \texttt{!} : \forall_\alpha.\alpha\ \texttt{ref}\ \to \alpha | ||
</math></center><br/> | </math></center><br/> | ||
Linia 156: | Linia 156: | ||
Czyli, np.: | Czyli, np.: | ||
<center><math>A, \texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref} \vdash (r := (\texttt{function}\ x \to x + 42))</math></center><br/> | <center><math> A, \texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref} \vdash (r := (\texttt{function}\ x \to x + 42))</math></center><br/> | ||
<center><math> | <center><math> | ||
A, \texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref} | A, \texttt{r}: \forall_\alpha.(\alpha \to \alpha)\ \texttt{ref} | ||
\vdash \texttt{!r}\ \texttt{true} : \texttt{bool} | \vdash \texttt{!r}\ \texttt{true} : \texttt{bool} | ||
Linia 163: | Linia 163: | ||
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. | * 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. | ||
Linia 226: | Linia 226: | ||
'''''val''' f : 'a -> 'a -> 'a'' | '''''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ć <math>\eta</math>-ekspansję, tzn.\ dodać formalny argument procedury. Jeżeli nie ma (imperatywnych) przeciwwskazań, to uzyskamy | 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 | ||
procedurę równoważną. | procedurę równoważną. | ||
Wersja z 08:56, 27 lip 2006
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ą.