Izpeljava tipov

Kako programski jeziki uporabljajo tipe

Skoraj vsi programski jeziki imajo tipe, razlikujejo pa se po tem, kako se le-ti uporabljajo.

Kako striktni so tipi

Tipi so lahko bolj ali manj striktni. Če so popolnoma striktni, ima vsak izraz v veljavnem programu tip (OCaml, Standard ML, Haskell, Java, C++). Lahko se zgodi, da veljavni program nima tipa, ali vsaj ne takega, ki bi dobro opisal njegovo delovanje (Javascript, Python).

Primer: nabori v Pythonu imajo zelo ohlapen tip tuple, ki ne pove nič več kot to, da gre za urejeno večterico:

>>> type((1, 'foo', False))
<type 'tuple'>

V OCamlu so tipi striktni. Tip urejene trojice je bolj informativen:

# (1, "foo", false) ;;
- : int * string * bool = (1, "foo", false)

Dinamični in statični tipi

Poznamo delitev glede na fazo, v kateri se uporabijo tipi:

Preverjanje in izpeljevanje tipov

Programski jezik lahko tipe preverja ali izpeljuje:

Večina jezikov dopušča kombinacijo obeh tehnik. V OCamlu in Haskellu lahko predpišemo tip, čeprav bi ga programski jezik lahko izpeljal tudi sam. C++, Java in C# omogočajo izpeljavo tipov v omejenem obsegu, na primer pri tipih lokalnih spremenljivk.

Monomorfni in polimorfni tipi

Tipi so lahko:

Poznamo več vrst polimorfizma, danes bomo obravnavali parametrični polimorfizem.

Izpeljava tipov

Programski jeziki kot so OCaml, Standard ML in Haskell imajo polimorfne tipe, ki jih izpeljejo z algoritmom, ki sta ga razvila Hindley in Milner.

Kakšen tip ima funkcija λ x . x, oziroma v OCamlu fun x -> x? Možnih je veliko odgovorov:

Od vseh je zadnji najbolj splošen, ker lahko vse ostale dobimo tako, da parameter β zamenjamo s kakim drugim tipom. Pravimo, da je β → β glavni tip funkcije fun x -> x.

Definicija: Tip izraza je glavni, če lahko vse njegove tipe dobimo tako, da v glavnem tipu parametre zamenjamo s tipi (ki lahko vsebujejo nadaljne parameter).

OCaml je načrtovan tako, da ima vsak veljaven izraz glavni tip, ki ga OCaml izpelje sam. (Izjema so rekurzivne polimorfne funkcije, kjer mora programer sam opredeliti tip, saj algoritem za izračun glavniega tip rekurzivne funkcije ne obstaja.)

Postopek izpeljave glavnega tipa

Glavni tip izraza e izpeljemo v dveh fazah:

  1. Izračunamo kandidata za tip e, ki vsebuje neznanke, in enačbe, ki jim morajo neznanke zadoščati.
  2. Rešimo enačbe s postopkom združevanja.

Druga faza se lahko zalomi, če se izkaže, da enačbe nimajo rešitve.

Prva faza

V prvi fazi izračunamo kandidata za tip in nabiramo enačbe, ki morajo veljati:

Druga faza: združevanje

Imamo množico enačb E

l₁ = d₁
l₂ = d₂
l₃ = d₃
...
lᵢ = dᵢ

v neznankah α, β, γ, δ, … Rešujemo z naslednjim postopkom:

  1. Imamo seznam rešitev r, ki je na začetku prazen.

  2. Če je E prazna množica, vrnemo rešitev r.

  3. Sicer iz E odstranimo katerokoli enačbo l = d in jo obravnvamo:

Kako to deluje, si poglejmo na primerih.

Primer 1

Izpelji glavni tip funkcije

fun x -> x + 3

Odgovor: int -> int

Primer 2

Izpelji glavni tip izraza

if 3 < 5 then (fun x -> x) else (fun y -> y + 3)

Odgovor: int -> int

Churchovi numerali

Kakšen je tip števila 3?

0 = (λ f x . x)
1 = (λ f x . f x)
2 = (λ f x . f (f x))
3 = (λ f x . f (f (f x)))

To naj izračuna OCaml:

let zero  = (fun f x -> x) ;;
let one   = (fun f x -> f x) ;;
let two   = (fun f x -> f (f x)) ;;
let three = (fun f x -> f (f (f x))) ;;
Churchovi-Scottovi numerali

Kakšen je tip števila 3?

0 = (λ f x . x)
1 = (λ f x . f 0 x)
2 = (λ f x . f 1 (f 0 x))
3 = (λ f x . f 2 (f 1 (f 0 x)))

To naj izračuna OCaml: