Odvodenie typu

Typová rekonštrukcia

V rámci predchádzajúcich prednášok sme venovali algoritmu typovej kontroly, ktorá vyžadovala explicitné typové anotácie. Pri $\lambda$-abstrakcii je nutné explicitne uviesť typ jej argumentu, ako aj typ celého termu. V dnešnej prednáške sa budeme venovať algoritmu typovej rekonštrukcie (odvodenie typu), vďaka ktorému je možné vynechať niektoré resp. aj všetky typové anotácie.

Poznámka

Zavedenie typovej rekonštrukcie do rozšírení jazyka ako sú súčtové typy alebo podtypy je náročný a obšírny proces. Pre jednoduchosť a demonštratívne účely sa budeme venovať len jednoducho typovanému $\lambda$-kalkulu so základnými typmi.

Poznámka

Pre praktickú demonštráciu uvedených postupov budú využité reálne funkcionálne jazyky OCaml a Haskell, v ktorých je takéto odvodenie typov implementované. Tieto jazyky sú statický a implicitne typované, avšak podporujú aj explicitné uvedenie typov.

Odvodenie typu

Odvodenie typu je proces určenia vhodných typov výrazu na základe toho ako sú použité. Napríklad môžeme jednoducho odvodiť, že vo výraze $$\lambda f. f~3,$$ $3$ - argument funkcie je konštanta typu $Nat$ a $f$ je funkcia (nie kvôli svojmu názvu $f$, ale kvôli tomu, že je aplikovaná na nejaký argument). Na základe uvedených podmienok (obmedzení) je možné odvodiť: $$f:Nat \rightarrow X$$ a celý výraz je typu: $$\lambda f. f~3:(Nat \rightarrow X) \rightarrow X.$$

$X$ reprezentuje typovú premennú. Keďže funkcia očakáva na vstupe funkciu, ktorá má parameter typu $Nat$, avšak nevieme aký bude výstup. Pojem typová premenná je vysvetlený nižšie.

Pre overenie korektnosti nášho uvažovania prepíšeme daný term do jazyka OCaml, prostredníctvom ktorého sa následne pokúsime odvodiť typ.

utop # fun f -> f 3;; 
- : (int -> 'a) -> 'a = <fun>

Poznámka

V jazyku OCaml sú typové premenné označované: 'a, 'b, 'c,....

Upravíme náš program a znova sa pokúsime odvodiť typ:

utop # fun f -> f (f 3);; 
- : (int -> int) -> int = <fun>

utop # fun f -> f (f "ahoj");; 
- : (string -> string) -> string = <fun>

utop # fun f -> f (f 3, f 4);; 
Error: This expression has type int but an expression was expected of type
         'a * 'b

Pozrime sa bližšie na výstup z interpretera jazyka OCaml. Pouvažujte, ako v prvom prípade OCaml vedel, že výstup funkcie $f$ je typu $Int$. Pretože vo výraze $f~(f~3)$ je vnútorné $f$ aplikované na argument typu $Int$ a tento výstup je vstupom pre vonkajšie $f$.

Definícia:
Ak je term správne typovaný potom jeho typ môže byť odvodený.

Príklad

Majme nasledujúci term: $$ \begin{array}{l} let~sq=\lambda z . z*z~in \\ \quad \lambda f. \lambda x. \lambda y.\\ \qquad if ~ (f~x~y) \\ \qquad then ~ (f~(sq ~ x)~y) \\ \qquad else ~ (f~x~(f ~ x~y)) \end{array} $$ Z uvedeného termu je možné vydedukovať jeho typ naivným spôsobom nasledovne:

  • Na premennú $z$ je aplikovaný operátor násobenia, to znamená $z:Nat$ a preto:
    • $\lambda z . z*z: Nat \rightarrow Nat,$
    • $sq: Nat \rightarrow Nat.$
  • Vidíme, že $f$ musí byť nejakého typu v tvare: $f: T_1 \rightarrow T_2 \rightarrow Bool$, keďže je aplikované na dva argumenty $x~y$ a táto aplikácia sa nachádza na mieste podmienky v príkaze $if$, preto výsledok musí byť typu $Bool$.
  • Vo vetve then je $f$ aplikované na $sq~x$ ako prvý argument, to znamená $T_1=Nat$.
  • Vo vetve else je $f$ aplikované na $f ~ x~y$ ako druhý argument, to znamená $T_2=Bool$.
  • Odvodili sme, že:
    • $f:Nat\rightarrow Bool \rightarrow Bool$,
    • $x:Nat$,
    • $y:Bool$.
  • Náš term očakáva argumenty $f,x,y$ a vráti hodnotu typu $Bool$.
  • Typ termu je: $(Nat\rightarrow Bool \rightarrow Bool)\rightarrow Nat \rightarrow Bool \rightarrow Bool.$ Pre overenie korektnosti nášho uvažovania prepíšme program do jazyka OCaml:
utop # 
let sq=fun z -> z*z in
   fun f -> fun x -> fun y -> 
     if f x y 
     then (f (sq x) y)
     else (f x (f x y));;
- : (int -> bool -> bool) -> int -> bool -> bool = <fun>

Obsah

  1. Typová premenná.
  2. Typová substitúcia.
  3. Odvodenie typu na základe obmedzení (z ang. constraint-based typing).
    1. Obmedzenie.
    2. Typová relácia s obmedzeniami a príslušné typové pravidlá.
    3. Množina obmedzení $C$.
  4. Unifikačný algoritmus - riešenie množiny obmedzení.
  5. Najvšeobecnejší unifikátor (z ang. most general unifier).
  6. Najvšeobecnejší typ (z ang. principle type).

Postup

Majme dané ľubovoľný term $t$ a kontext $\Gamma$.

  1. Prostredníctvom typových pravidiel pre novú typovú reláciu, odvodíme typ $S$ a množinu obmedzení $C$ pre term $t$ a kontext $\Gamma$.
  2. Nájdeme riešenie množiny obmedzení $C$, prostredníctvom algoritmu unifikácie.
    1. Vstup: množina obmedzení.
    2. Výstup: najvšeobecnejší unifikátor - substitúcia $\sigma$.
  3. Aplikujeme substitúciu $\sigma$ na odvodený typ $S$ a získame najvšeobecnejší typ $T$.

Typové premenné

V jednoducho typovanom $\lambda$-kalkule je definovaná množina základných typov ako napr. Bool, Nat, Unit, ktorá zároveň obsahuje aj neinterpretované typy $T_1,T_2...$. Neinterpretované typy je možné definovať ako typové premenné.

Množina $\chi$ obsahujúca typové premenné $X,Y,Z$ je definovaná nasledovne: $$\chi=\{X,Y,Z,...\}.$$

  • Z typových premenných a typových konštruktorov je možné vytvárať typové výrazy.
  • Substitúcia typovej premennej konkrétnym typom vytvára typovú inštanciu.
  • Množinu všetkých typov označíme ako $Types$.

Príklad: Typové výrazy:

$X \rightarrow Y,\quad $ $X + Y \rightarrow Z,\quad $ $X \rightarrow Nat,\quad $ $Bool$ a podobne.

  • Množina $Types=\{T_1,T_2,...,Bool, Nat, Unit, ...\}$ obsahuje konkrétne typy jazyka.
  • Množina $\chi=\{X,Y,Z,...\}$ obsahuje typové premenné.

Typová substitúcia

Neformálne, operáciu typovej substitúcie (ďalej len substitúcie, kde je z kontextu jasné, že sa jedná o typovú substitúciu) je možné chápať ako dosadenie konkrétneho typu za typovú premennú.

Špecifikácia zobrazenia substitúcie

Formálne substitúcia $\sigma$ je konečné zobrazenie špecifikované nasledovne: $$\sigma: \chi \rightarrow Types,$$ z typových premenných do typov. To znamená, aplikáciou zobrazenia $\sigma$ na určitý typový výraz získame konkrétny typ - inštanciu typového výrazu.

Príklad

Nech $X$ je typová premenná a substitúcia $\sigma$ definovaná nasledovne: $$\sigma=[X \mapsto Bool].$$ Potom aplikácia substitúcie $\sigma$ na typový výraz $X \rightarrow X$ vytvorí nasledujúcu inštanciu typového výrazu: $$\sigma(X \rightarrow X)=Bool \rightarrow Bool.$$

Poznámka

Definujeme nasledujúce funkcie:

  • Definičný obor substitúcie $$dom(\sigma) = \chi,$$ je množina typových premenných.
  • Obor hodnôt substitúcie $$range(\sigma)=Types,$$ je množina typov, ktoré je možné dosadiť za typovú premennú.

Príklad

Nech je daná substitúcia $\sigma=[X \mapsto Nat \rightarrow Bool]$. Potom $$dom(\sigma)=X, \quad range(\sigma)= Nat \rightarrow Bool.$$

Definícia zobrazenia substitúcie

Nech $\sigma$ je substitúcia, $X$ je typová premenná, $T$ je typ. Potom konečné zobrazenie typovej substitúcie je možné definovať nasledovne: $$ \begin{array}{lcl} \sigma(X) & = & \begin{cases} T \quad ak \quad (X \mapsto T) \in \sigma \\ X \quad ak \quad X \notin dom(\sigma) \\ \end{cases} \\ \sigma(Nat) & = & Nat \\ \sigma(Bool) & = & Bool \\ \sigma(T_1 \rightarrow T_2) & = & \sigma T_1 \rightarrow \sigma T_2 \end{array} $$

Substitúcia aplikovaná na kontext: $$ \begin{array}{lcl} \sigma(x_1:T_1,... ,x_n:T_n) & = & x_1:\sigma T_1,... , \sigma x_n: \sigma T_n. \end{array} $$

Analogicky v prípade aplikácie substitúcie na term $t$, je substitúcia aplikovaná na všetky typy, ktoré sa nachádzajú v typových anotáciách $t$.

Nech $\sigma, \gamma$ sú substitúcie, potom kompozícia typových substitúcií $\sigma \circ \gamma$ je definovaná nasledovne:

$$ \sigma \circ \gamma = \left[ \begin{array}{ll} X \mapsto \sigma(T)\quad & \text{pre každé } (X \mapsto T) \in \gamma\\ X \mapsto T \quad & \text{pre každé } (X \mapsto T) \in \sigma \text{ také, že } X \notin dom(\gamma)\\ \end{array} \right] $$

Poznámka

Je potrebné si uvedomiť, že: $$(\sigma \circ \gamma)S = \sigma (\gamma S) $$

Príklad

Nech je daná substitúcia: $$\sigma=[A \mapsto Nat] \circ [B \mapsto A \rightarrow Bool],$$ typový výraz: $$T=A \rightarrow B \rightarrow C.$$ Aplikujte $\sigma$ na $T$.

Riešenie

$$ \begin{array}{lcl} \sigma T & = & \sigma (A \rightarrow B \rightarrow C)\\ & = & \sigma A \rightarrow \sigma (B \rightarrow C)\\ & = & \sigma A \rightarrow \sigma B \rightarrow \sigma C \\ & = & [A \mapsto Nat] A \rightarrow [A \mapsto Nat] (A \rightarrow Bool) \rightarrow [A \mapsto Nat] C \\ & = & Nat \rightarrow ([A \mapsto Nat] A \rightarrow [A \mapsto Nat] Bool) \rightarrow C \\ & = & Nat \rightarrow ( Nat \rightarrow Bool) \rightarrow C \\ \end{array} $$

Poznámka

Pouvažujte aký výsledok by by vznikol ak by bolo zmenené poradie kompozície substitúcie následovne: $$\sigma'=[B \mapsto A \rightarrow Bool] \circ [A \mapsto Nat].$$

Odvodenie typu na základe obmedzení

V tejto kapitole si predstavíme algoritmus, ktorý pre ľubovoľný term $t$ a kontext $\Gamma$ vypočíta množinu obmedzení - rovností medzi typovými výrazmi (prípadne aj typovými premennými), ktorá musí byť splnená v akékoľvek riešení pre ($\Gamma, t$).

Definícia: Množina obmedzení $C$ je množina rovností v tvare: $$C=\{S_i=T_i^{i\in 1...n}\},$$ kde $S_i, T_i$ sú ľubovoľné typy.

Obmedzenie má tvar $T_1 = T_2$, pre akékoľvek $T_1, T_2$.

Definícia: Unifikácia rovnosti $S$ a $T$ je taká substitúcia $\sigma$, že $$\sigma S = \sigma T.$$ Hovoríme, že substitúcia $\sigma$ unifikuje (spĺňa) množinu obmedzení $C$, ak unifikuje každú rovnosť v $C$.

Definícia: Typová relácia s obmedzeniami: $$\Gamma \vdash t:T~|~C,$$ ktorú je možné prečítať ako "term $t$ odvodzuje typ $T$ v prostredí $\Gamma$ a generuje množinu obmedzení $C$".

Ak uvažujeme o tejto relácii ako o funkcii, potom:

  • dvojbodka $:$ v strede oddeľuje vstup od výstupu,
  • vstup: $\Gamma, t$, chceme zistiť či term $t$ v prostredí $\Gamma$ má typ,
  • výstup: typ $T$ a množina obmedzení $C$, ktorú musí typ $T$ spĺňať aby term $t$ v prostredí $\Gamma$ bol správne typovaný.

Jednoducho typovaný $\lambda$-kalkul

Pripomeňme si fragment nášho jazyka jednoducho typovaného $\lambda$-kalkulu s NBL, pre ktorý následne zadefinujeme typové pravidlá pre typovú reláciu s obmedzeniami: $$ \begin{array}{lcl} t & ::= & x ~|~ \lambda x :T.t ~|~ \lambda x.t ~|~t~t ~|~ 0 ~|~ true ~|~ false ~|~ succ~t ~|~\\ & & ~|~ pred~t ~|~ iszero~t ~|~~if~t~then~t~else~t~ \\ v & ::= & \lambda x:T.t ~|~ \lambda x.t ~|~ true ~|~ false ~|~ nv\\ nv & ::= & 0 ~|~ succ~nv\\ T & ::= & A ~|~ T \rightarrow T ~|~ X\\ A & ::= & Nat ~|~ Bool \end{array} $$

Poznámka

Jazyk $\lambda_\to$ bol rozšírený o $\lambda$-abstrakciu $\lambda x.t$ bez explicitnej typovej anotácie, čím bola odstránená nutnosť explicitne uvádzať akékoľvek typy. Zároveň bola v jazyku ponechaná aj pôvodná abstrakcia, kde je táto možnosť stále ponechaná.

Typové pravidlá pre typovú reláciu s obmedzeniami

Pravidlá pre konštanty sú axiómy a pravidlo pre premennú kontroluje jej výskyt v typovom kontexte. Tieto pravidla negenerujú obmedzenia.


Pravidlá pre abstrakciu sú dve a to pre prípad kde je uvedená typová anotácia parametra abstrakcie (CT-Abs) alebo nie je (CT-AbsInf). V druhom prípade (CT-AbsInf) je potrebné zaviesť novú typovú premennú $X$ (niekedy v ang. nazývaná fresh variable).


$$X \text{ nová typová premenná }(fresh)$$

Pravidlo pre aplikáciu vyžaduje zavedenie novej typovej premennej $X$ a vytvára obmedzenie $C$, ktoré je zjednotením obmedzení oboch predpokladov a rozširuje množinu o rovnosť $T_1=T_2\rightarrow X$, pričom $X$ musí byť nová typová premenná.


$$X \text{ nová typová premenná }(fresh)$$

Pravidlá pre termy If, Succ, Pred, Iszero vytvárajú nové obmedzenie $C$ nasledovne:

  • V prípade pravidla pre $CT-If$ nové obmedzenie $C$ pozostáva zo zjednotení obmedzení zo všetkých predpokladov pravidla a rozširuje ho o rovnosti $T_1=Bool, T_2=T_3$.
  • V prípade pravidiel $CT-Succ, CT-Pred, CT-Iszero$ nové obmedzenie pozostáva zo zjednotenia obmedzenia z predpokladu pravidla o rovnosť $T=Nat$.


Definícia:
Nech je daný nasledujúci sekvent: $$\Gamma \vdash t:S ~|~ C.$$ Potom riešenie štvorice $$(\Gamma,t,S,C)$$ je taká dvojica $$(\sigma, T),$$ že $\sigma$ spĺňa $C$ a zároveň: $$\sigma S = T.$$

Poznámka

Je potrebné si uvedomiť, že pri typovej rekonštrukcii máme na vstupe typový kontext $\Gamma$ a term $t$. Princíp ostáva rovnaký ako pri typovej kontrole. Aplikujeme typové pravidlá, pomocou ktorých zostrojíme typový strom a následne spätným prechádzaním stromu získame typ $S$ a príslušnú množinu obmedzení $C$.

Hlavným rozdielom je, že pri typovej rekonštrukcii je strom vždy možné zostrojiť, avšak pri nesprávne typovaných termoch, resp. termoch ktoré nemajú typ je v strome vytvorená nekonzistentná množina obmedzení.

Príklad

Majme dané $\Gamma= \{\}$ a $t = \lambda x. if~x~then~1~else~0$. Prostredníctvom typovej rekonštrukcie pokúste nájsť typ termu $t$.

Riešenie

Máme daný sekvet: $$\vdash \lambda x. if~x~then~1~else~0: S~|~C.$$ V prvom kroku aplikáciou typových pravidiel zostrojíme typový strom pre $\Gamma, t$ bez typových anotácií a množiny obmedzení:


V druhom kroku podľa typových pravidiel odvodíme príslušný typ $S$ a množinu obmedzení $C$.


Následne z odvodenej množiny obmedzení $C=\{X=Bool, Nat=Nat\}$ určíme také riešenie $\sigma$, že jeho aplikácia na odvodený typ $S=X\rightarrow Nat$ poskytne taký typ $T$, že platí: $$\vdash \lambda x. if~x~then~1~else~0:T.$$

V tomto prípade je riešenie zjavné vzhľadom na odvodenú množinu obmedzení $$C=\{X=Bool, Nat=Nat\},$$ a odvodený typ $$S=X\rightarrow Nat,$$ je naše riešenie $\sigma$ nasledovné: $$\sigma=[X \mapsto Bool],$$ pričom:

$$ \begin{array}{lcl} \sigma S &=& [X \mapsto Bool](X\rightarrow Nat) \\ &=& [X \mapsto Bool]X \rightarrow [X \mapsto Bool]Nat \\ &=& Bool \rightarrow Nat\\ &=& T. \end{array} $$

Riešenie v jazyku OCaml:

utop # fun x -> if x then 1 else 0;;
- : bool -> int = <fun>

Najvšeobecnejší unifikátor a najvšeobecnejší typ

Majme danú identickú funkciu: $$id=\lambda x:T.x:T \rightarrow T.$$ Tento term je správne typovaný pre ľubovoľný typ $T$. Napríklad: $$\lambda x:Nat.x:Nat \rightarrow Nat,$$ pričom $\sigma=[T\mapsto Nat]$. Aj napriek tomu, že táto funkcia sa stále správa rovnako, v prípade potreby aplikácie tejto funkcie na argument iného typu, je nutné napísať vlastnú funkciu s príslušným typom: $$ \begin{array}{l} \lambda x:Bool.x:Bool \rightarrow Bool\\ \lambda x:Nat\rightarrow Bool.x:(Nat\rightarrow Bool) \rightarrow (Nat\rightarrow Bool)\\ ..., \end{array} $$ čo v konečnom dôsledku vytvára nekonečné množstvo unifikátorov: $$ \begin{array}{l} \sigma=[T\mapsto Bool]\\ \sigma=[T\mapsto Nat\rightarrow Bool]\\ .... \end{array} $$

Takéto riešenie z hľadiska softvérového inžinierstva nie je žiadúce. Tento problém je možné vyriešiť zavedením polymorfizmu, kde namiesto konkrétneho typu sa v typových anotáciách budú môcť vyskytovať typové premenné a typová rekonštrukcia odvodí konkrétny typ podľa potreby (napr. podľa typu argumentu).

Demonštrácia uvedeného v jazyku OCaml:

utop # let id : int -> int = fun x -> x;;
val id : int -> int = <fun>

utop # id 4;;
- : int = 4

utop # id "ahoj";;
Error: This expression has type string but an expression was expected of type
         int

utop # let id_poly  = fun x -> x;;
val id_poly : 'a -> 'a = <fun>

utop # id_poly 4;;
- : int = 4

utop # id_poly "ahoj";;
- : string = "ahoj"

Najvšeobecnejší unifikátor

Z vyššie uvedeného vyplýva, že našim cieľom nie len nájsť ľubovoľné riešenie ($\sigma$, $T$) aby platilo $\sigma S = T$, ale hľadáme najvšeobecnejšie riešenie (unifikátor) $\sigma_{mgu}$. Zároveň platí, že ak existuje také $\sigma$, že $\sigma S = T$, potom existuje aj najvšeobecnejší unifikátor.

Definícia: Unifikátor $\sigma$ je všeobecnejší (more general) ako unifikátor $\sigma_2$, ak existuje taký unifikátor $\sigma_1$, že platí: $$\sigma_1 \circ \sigma = \sigma_2.$$

Definícia: Najvšeobecnejší unifikátor (z ang. Most General Unifier (MGU)) dvoch typov $S,T$ je taký unifikátor $\sigma$, že platí:

  • $\sigma$ je unifikátor typov ($S,T$),
  • $\sigma$ je všeobecnejší ako ľubovoľný iný unifikátor ($S,T$).

Poznámka

Vo všeobecnosti najvšeobecnejší unifikátor nemusí byť jedinečný.

Príklad

Majme term $$\lambda x:T.x:T \rightarrow T.$$ Unifikátor $$\sigma_1=[T\mapsto X \rightarrow X],$$ je všeobecnejší ako $$\sigma_2=[T\mapsto Nat \rightarrow Nat],$$ avšak menej všeobecný ako najvšeobecnejší unifikátor: $$\sigma_{mgu}=[T\mapsto X].$$

Poznámka

Vlastnosť unifikátora byť "viac všeobecný" ako iný unifikátor, je možné chápať ako substitúciu, ktorá umožňuje vytvoriť väčšie množstvo typov.

Najvšeobecnejší typ

Ako je možné vidieť v predchádzajúcej kapitole, term $\lambda x.x$ má nekonečné množstvo inštancií typu, pri ktorých je správne typovaný. Avšak v takomto prípade naším cieľom pri typovom odvodení nie je len nájsť "nejaký" typ, ale nájsť najvšeobecnejší typ.

Najvšeobecnejší typ (z ang. principal type) je taký typ, ktorý povoľuje vytvorenie najväčšieho množstva typových inštancií, ktoré vznikajú aplikáciou substitúcie.

Príklad

Majme term $\lambda x.x$. Potom:

  • Typ $Nat \rightarrow Nat$ nepovoľuje vytvorenie ďalších typových výrazov, keďže po aplikácií ľubovoľnej substitúcie výsledok vždy bude $Nat \rightarrow Nat$.
  • Na typ $(X\rightarrow X) \rightarrow (X\rightarrow X)$ je možné aplikovať substitúciu a vytvoriť tak ďalšie typy, avšak tvar tohto typu obmedzuje možnosti aké typy môžu vzniknúť po aplikácii ľubovoľnej substitúcie. V tomto prípade napríklad nie je možné vytvoriť typ $Bool \rightarrow Bool$, ktorý je možné získať z typu $X\rightarrow X$.

Preto hovoríme, že typ $(X\rightarrow X) \rightarrow (X\rightarrow X)$ je viac všeobecný ako $Nat \rightarrow Nat$ avšak je menej všeobecný ako $(X\rightarrow X)$, ktorý je najvšeobecnejší typ pre term $\lambda x.x$.

Nájdenie najvšeobecnejšieho unifikátora prostredníctvom unifikačného algoritmu

Aby sme našli najvšeobecnejší typ potrebujeme nájsť najvšeobecnejšiu substitúciu (unifikátor) $\sigma$, ktorá bude spĺňať danú množinu obmedzení. Avšak nie vždy je množina obmedzení také prostá ako v predchádzajúcich príkladoch a nájdenie najvšeobecnejšieho unifikátora (ak vôbec existuje) sa stáva zložitým problémom.

Najvšeobecnejší unifikátor následne aplikujeme na odvodený typ a tým získame najvšeobecnejší typ.

Pre výpočet najvšeobecnejšieho unifikátora danej množiny obmedzení využijeme myšlienku Hindleyho a Milnera, ktorí na tento účel upravili Robinsonov unifikačný algoritmus.

Poznámka

Typová rekonštrukcia sa nazýva aj typový systém alebo algoritmus Hindleyho a Milnera (z ang. Hindley-Milner algorithm).

Unifikačný algoritmus


Algoritmus: Unifikačný algoritmus

Vstup: Množina obmedzení $C$

Výstup: Najvšeobecnejší unifikátor $\sigma_{mgu}$ (ak existuje), zlyhanie inak


$$ \begin{array}{lll} unify(C) &=& if~C=\{\}, ~then~[~] \\ & & else \\ & & \quad \quad let~ \{S=T\} \cup C'=C~in \\ & & \quad if~ S=T \\ & & \quad \quad then ~ unify(C')\\ & & \quad else~if~ S=X ~a~ X\notin T \\ & & \quad \quad then ~ unify([X\mapsto T]C') \circ [X\mapsto T]\\ & & \quad else~if~ T=X ~a~ X\notin S \\ & & \quad \quad then ~ unify([X\mapsto S]C') \circ [X\mapsto S]\\ & & \quad else~if~ S=S_1 \rightarrow S_2 ~~and~~ T = T_1 \rightarrow T_2 \\ & & \quad \quad then ~ unify(C' \cup \{S_1=T_1, S_2 = T_2\}) \\ & & \quad else ~ fail \end{array} $$


Unifikačný algoritmus slovne

  • Rekurzívne voláme funkciu $unify(C)$ pokiaľ množina obmedzení $C$ nie je prázdna $C=\{~\}$. Možnosti:
    • Priradenie novej substitúcie do $\sigma_{mgu}$.
    • Priradenie nového obmedzenia do $C$.
    • Fail, množina obmedzení $C$ je inkonzistentná.
  • Vyberieme prvú rovnosť $S=T$ z množiny obmedzení $\{S=T\} \cup C'=C$ ($C'$ zvyšok množiny obmedzení).
    • Ak zvolená rovnosť je v tvare $T=T$, napr. $X=X, Nat=Nat, Nat\rightarrow Nat = Nat\rightarrow Nat...$.
      • Žiadne nové obmedzenie, ani substitúcia.
      • Voláme rekurzívne funkciu $unify(C')$ so zvyškom množiny obmedzení.
    • Ak vo zvolenej rovnosti $S=T$ je na pravej strane $X$ (*to znamená *$S=X$), a zároveň $X$ sa nenachádza v $T$, pričom $X$ je typová premenná:
      • Substitúcia $T$ za $X$ v množine obmedzení $C'$ - eliminácia typovej premennej $X$.
      • Rekurzívne volanie funkcie $unify([X\mapsto T]C') \circ [X\mapsto T]$ s aktualizovanou množinou obmedzení po substitúcii. Zároveň vzniká časť riešenia $unify(C) = \sigma_{mgu}= ... \circ [X\mapsto T]$.
    • Ak vo zvolenej rovnosti $S=T$ je na ľavej strane $X$ (*to znamená *$T=X$) a zároveň $X$ sa nenachádza v $S$, pričom $X$ je typová premenná:
      • Substitúcia $S$ za $X$ v množine obmedzení $C'$ - eliminácia typovej premennej $X$.
      • Rekurzívne volanie funkcie $unify([X\mapsto S]C') \circ [X\mapsto S]$ s aktualizovanou množinou obmedzení po substitúcii. Zároveň vzniká časť riešenia $unify(C) = \sigma_{mgu}= ... \circ [X\mapsto S]$.
    • Ak zvolená rovnosť je v tvare $T_1\rightarrow T_2 = S_1 \rightarrow S_2$
      • Pridáme nové obmedzenia do množiny obmedzení $C'$ v tvare: $T_1=S_1, T_2=S_2$.
      • Rekurzívne voláme $unify(C' \cup \{S_1=T_1, S_2 = T_2\})$.

Poznámka

V prípade, že rovnosť je v tvare: $$T_1 \rightarrow T_2 = S_1 \rightarrow S_2,$$ podľa algoritmu postupujeme vytvorením dvoch nových obmedzení: $$S_1=T_1, S_2 = T_2.$$ V prípade ak v rovnosti: $$T_1 \rightarrow T_2 = S_1 \rightarrow S_2$$ sa nenachádza rovnaký počet typových konštruktorov funkčného typu: $$Nat \rightarrow Bool \rightarrow F = X \rightarrow Z,$$ je potrebné si uvedomiť, že konštruktor $\rightarrow$ je pravoasociatívny.

To znamená

$$ \begin{array}{l} Nat \rightarrow Bool \rightarrow F = X \rightarrow Z\\ Nat \rightarrow (Bool \rightarrow F) = X \rightarrow Z, \end{array} $$ pričom vznikajú nasledujúce dve rovnosti: $$ \begin{array}{l} Nat = X, \\ Bool \rightarrow F = Z. \end{array} $$

Príklad

Majme dané $\Gamma=\{~\}$ a $t=(\lambda x.x) (\lambda y.y)$. Nájdite najvšeobecnejší typ pre term $t$ v typovom prostredí $\Gamma$.

Riešenie

Máme daný sekvet: $$\vdash (\lambda x.x) (\lambda y.y): S~|~C.$$ V prvom kroku aplikáciou typových pravidiel zostrojíme typový strom pre $\Gamma, t$ bez typových anotácií a množiny obmedzení:


V druhom kroku podľa typových pravidiel odvodíme príslušný typ $S$ a množinu obmedzení $C$.


Odvodili sme typ $X$ a množinu obmedzení $C=\{A\rightarrow A = (B \rightarrow B) \rightarrow X\}$.

Vypočítame najvšeobecnejší unifikátor $\sigma_{mgu}$ využitím unifikačného algoritmu. $$ \begin{array}{lll} riadok & výpočet & \\ \hline 1 & unify(\{A\rightarrow A = (B \rightarrow B) \rightarrow X\}) &= \\ 2 & unify(\{A = B \rightarrow B, A = X\}\cup \{~\}) &= & \\ 3 & unify([A \mapsto B \rightarrow B] \{A = X\}) \circ [A \mapsto B \rightarrow B] &= & \\ 4 & unify(\{B \rightarrow B = X\}) \circ [A \mapsto B \rightarrow B] &= & \\ 5 & unify([X \mapsto B \rightarrow B]\{~\}) [X \mapsto B \rightarrow B] \circ [A \mapsto B \rightarrow B] &= & \\ 6 & unify(\{~\}) [X \mapsto B \rightarrow B] \circ [A \mapsto B \rightarrow B] &= & \\ 7 & [~] \circ [X \mapsto B \rightarrow B] \circ [A \mapsto B \rightarrow B] &= & \\ 8 & [X \mapsto B \rightarrow B] \circ [A \mapsto B \rightarrow B] &= & \\ 9 & \sigma_{mgu} & & \end{array} $$

Aplikujeme vypočítaný najvšeobecnejší unifikátor $\sigma_{mgu}$ na odvodený typ $X$:

$$ \begin{array}{lc} \sigma_{mgu} X & = \\ [X \mapsto B \rightarrow B] \circ [A \mapsto B \rightarrow B] X & = \\ [X \mapsto B \rightarrow B] X & = \\ B \rightarrow B & \end{array} $$ Náš term $t=(\lambda x.x) (\lambda y.y)$ je typu $B \rightarrow B$: $$(\lambda x:B.x) (\lambda y:B.y):B \rightarrow B.$$ V interpreteri jazykov Haskell a Ocaml je možné jednoducho overiť naše riešenie.

Haskell:

GHCi, version 9.4.7: https://www.haskell.org/ghc/  :? for help
ghci> term = (\x -> x) (\y -> y)
ghci> :t term
term :: p -> p
ghci> term 2 
2
ghci> :t term 2
term 2 :: Num p => p
ghci> term "text"
"text"
ghci> :t term "text"
term "text" :: String

Ocaml:

utop # let term = (fun x -> x) (fun y -> y);;
val term : '_weak1 -> '_weak1 = <fun>
utop # let id = fun x -> x;;
val id : 'a -> 'a = <fun>
utop # term 2;;
- : int = 2
utop # term ;;
- : int -> int = <fun>
utop # let term = (fun x -> x) (fun y -> y);;
val term : '_weak2 -> '_weak2 = <fun>
utop # term "text";;
- : string = "text"

Príklad

Majme dané $$\Gamma=\{add: Nat \rightarrow Nat \rightarrow Nat\}$$ a $$t=\lambda f. \lambda x. f~(add ~x~1).$$ Nájdite najvšeobecnejší typ pre term $t$ v typovom prostredí $\Gamma$.

Riešenie

  1. Zostrojíme štruktúru typového stromu:

Štruktúra typového stromu
Obr. 1: Štruktúra typového stromu

  1. Podľa pravidiel doplníme typy a obmedzenia:

Typový strom
Obr. 2: Typový strom

Z typového stromu sme získali nasledovné:

  • Odvodený typ: $$S=A \rightarrow B \rightarrow F.$$
  • Množina obmedzení: $$C=\{Nat \rightarrow Nat \rightarrow Nat = B \rightarrow D,~ D=Nat\rightarrow E,~ A=E \rightarrow F\}$$

Aplikujeme unifikačný algoritmus na množinu obmedzení $C$ a získame najvšeobecnejší unifikátor $\sigma_{mgu}$: $$unify(\{Nat \rightarrow Nat \rightarrow Nat = B \rightarrow D,~ D=Nat\rightarrow E,~ A=E \rightarrow F\})=\sigma_{mgu}$$

$Unify$
Iterácia
Množina obmedzení $C$
1. $\{Nat \rightarrow Nat \rightarrow Nat = B \rightarrow D,~ D=Nat\rightarrow E,~ A=E \rightarrow F\}$
2. $\{Nat = B,~ Nat \rightarrow Nat = D,~ D=Nat\rightarrow E,~ A=E \rightarrow F\}$
3. $\{Nat \rightarrow Nat = D,~ D=Nat\rightarrow E,~ A=E \rightarrow F\}$
4. $\{Nat \rightarrow Nat=Nat\rightarrow E,~ A=E \rightarrow F\}$
5. $\{Nat=Nat,~ Nat = E,~ A=E \rightarrow F\}$
6. $\{Nat = E,~ A=E \rightarrow F\}$
7. $\{A=Nat \rightarrow F\}$
8. $\{~\}$

Unifikáciou množiny obmedzení $C$ sme získali nasledujúci najvšeobecnejší unifikátor $\sigma_{mgu}$:

$$ \begin{array}{l} unify(\{Nat \rightarrow Nat \rightarrow Nat = B \rightarrow D,~ D=Nat\rightarrow E,~ A=E \rightarrow F\})= \\ [~]\circ [A \mapsto Nat \rightarrow F] \circ [E \mapsto Nat] \circ [D \mapsto Nat \rightarrow Nat] \circ [B \mapsto Nat]=\\ =\sigma_{mgu} \end{array} $$ Následne aplikujeme $\sigma_{mgu}$ na odvodený typ $S$ a získame najvšeobecnejší typ termu $t$ v typovom kontexte $\Gamma$:

$$\sigma_{mgu}(A \rightarrow B \rightarrow F)=(Nat \rightarrow F) \rightarrow Nat \rightarrow F.$$

Overíme naše riešenie prostredníctvom interpretéra jazyka OCaml:

utop # let add = fun x -> fun y -> x + y;;
val add : int -> int -> int = <fun>
utop # let t= fun f -> fun x -> f (add x 1);;
val t : (int -> 'a) -> int -> 'a = <fun>

Zdroje