Izpeljava tipov
Vsebina
7. Izpeljava tipov¶
7.1. Kako programski jeziki uporabljajo tipe¶
Skoraj vsi programski jeziki imajo tipe, razlikujejo pa se po tem, kako se le-ti uporabljajo.
7.1.1. 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)
7.1.2. Dinamični in statični tipi¶
Poznamo delitev glede na fazo, v kateri se uporabijo tipi:
Programski jezik ima statične tipe, če preveri ali izpelje tipe v statični fazi, se pravi ob prevajanju ali nalaganju kode, preden se koda požene. Primeri: C, C++, Java, C#, Standard ML, OCaml, Haskell, Swift, Scala.
Programski jezik ima dinamične tipe, če preverja tipe v dinamični fazi, se pravi, ko se koda izvaja. Primeri: Scheme, Racket, Javascript, Python.
7.1.3. Preverjanje in izpeljevanje tipov¶
Programski jezik lahko tipe preverja ali izpeljuje:
preverja jih, če programer v večji meri zapiše tipe spremenljivk, funkcij in atributov, programski jezik pa preveri, da so pravino uporabljeni. Primeri: C, C++, Java, C#.
izpeljuje jih, če programerju ni treba podajati tipov spremenljivk, funkcij in atributov (lahko pa jih, če to želi), programski jezik pa sam ugotovi, kakšnega tipa so. Primeri: OCaml, Standard ML, Haskell.
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.
7.2. Monomorfni in polimorfni tipi¶
Tipi so lahko:
monomorfni, če ima vsak izraz največ en tip
polimorfni, če ima lahko izraz hkrati več različnih tipov
Poznamo več vrst polimorfizma, danes bomo obravnavali parametrični polimorfizem.
7.3. 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:
int → int
bool → bool
int * int → int * int
α list → α list
za poljubenα
β → β
za poljubenβ
.
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.)
7.3.1. Postopek izpeljave glavnega tipa¶
Glavni tip izraza e
izpeljemo v dveh fazah:
Izračunamo kandidata za tip
e
, ki vsebuje neznanke, in enačbe, ki jim morajo neznanke zadoščati.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:
true
ima tipbool
, brez enačbfalse
ima tipbool
, brez enačbceloštevilska konstanta
0
,1
,2
, … ima tipint
, brez enačbspremenljivka ima svoj dani tip (tipe spremenljivk sproti beležimo v kontekstu)
aritmetični izraz
e₁ + e₂
:izračunamo tip
τ₁
izrazae₁
in dobimo še enačbeE₁
izračunamo tip
τ₂
izrazae₂
in dobimo še enačbeE₂
Tip izraza
e₁ + e₂
jeint
, z enačbamiE₁
,E₂
inτ₁ = int
,τ₂ = int
Podobno obravnavamo ostale aritmetične izrazee₁ * e₂
,e₁ - e₂
, …boolov izraz
e₁ && e₂
: obravnavamo podobno kot aritmetični izraz, le da uporabimo pričakovanibool
namestoint
.primerjava celih števil
e₁ < e₂
:izračunamo tip
τ₁
izrazae₁
in dobimo še enačbeE₁
izračunamo tip
τ₂
izrazae₂
in dobimo še enačbeE₂
Tip izraza
e₁ < e₂
jebool
, z enačbamiE₁
,E₂
inτ₁ = int
,τ₂ = int
pogojni stavek
if e₁ then e₂ else e₃
:izračunamo tip
τ₁
izrazae₁
in dobimo še enačbeE₁
izračunamo tip
τ₂
izrazae₂
in dobimo še enačbeE₂
izračunamo tip
τ₃
izrazae₃
in dobimo še enačbeE₃
Tip izraza
if e₁ then e₂ else e₃
jeτ₂
, z enačbamiE₁
,E₂
,E₃
,τ₁ = bool
,τ₂ = τ₃
urejeni par
(e₁, e₂)
:izračunamo tip
τ₁
izrazae₁
in dobimo še enačbeE₁
izračunamo tip
τ₂
izrazae₂
in dobimo še enačbeE₂
Tipi izraza
(e₁, e₂)
jeτ₁ × τ₂
, z enačbamiE₁
,E₂
.prva projekcija
fst e
:izračunamo tip
τ
izrazae
in dobimo še enačbeE
Uvedemo nova parametra
α
inβ
(se ne pojavljata vE
). Tip izrazafst e
jeα
, z enačbamiE
,τ = α × β
.druga projekcija
snd e
:izračunamo tip
τ
izrazae
in dobimo še enačbeE
Uvedemo nova parametra
α
inβ
. Tip izrazasnd e
jeβ
, z enačbamiE
,τ = α × β
.funkcija
fun x -> e
: uvedemo nov parameterα
in zabeležimo, da imax
tipα
, terizračunamo tip
τ
izrazae
(pri predpostavki, da imax
tipα
) in dobimo še enačbeE
Tip funkcije
fun x -> e
jeα → τ
z enačbamiE
aplikacija
e₁ e₂
:izračunamo tip
τ₁
izrazae₁
in dobimo še enačbeE₁
izračunamo tip
τ₂
izrazae₂
in dobimo še enačbeE₂
Uvedemo nov parameter
α
. Tip izrazae₁ e₂
jeα
, z enačbamiE₁
,E₂
,τ₁ = τ₂ → α
rekurzivna definicija
x = e
(kjer sex
pojavi ve
): uvedemo nov parameterα
, zabeležimo, da imax
tipα
, terizračunamo tip
τ
izrazae
(pri predpostavki, da imax
tipα
) in dobimo še enačbeE
Tip izraza
x
jeτ
, z enačbamiE
,α = τ
. Opomba: običajno na ta način definiramo rekurzivne funkcije, torej box
v resnici funkcija.
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:
Imamo seznam rešitev
r
, ki je na začetku prazen.Če je
E
prazna množica, vrnemo rešitevr
.Sicer iz
E
odstranimo katerokoli enačbol = d
in jo obravnvamo:če sta leva in desna stran povsem enaki, enačbo zavržemo ter gremo na korak 2
če je enačba oblike
α = d
, kjer jeα
neznanka:če se
α
pojavi vd
, postopek prekinemo, ker ni rešitvesicer smo našli rešitev za
α
, namrečα ↦ d
. Povsod vr
inE
zamenjamoα
zd
in vr
dodamo rešitevα ↦ d
če je enačba oblike
l = α
, kjer jeα
neznanka, imamo primer, ki je simetričen prejšnjemuče je enačba oblike
(l₁ → l₂) = (d₁ → d₂)
, vE
dodamo enačbil₁ = d₁
inl₂ = d₂
in gremo na korak 2če je enačba oblike
(l₁ × l₂) = (d₁ × d₂)
, vE
dodamo enačbil₁ = d₁
inl₂ = d₂
in gremo na korak 2če je enačba katerekoli druge oblike, na primer
(l₁ → l₂) = (d₁ × d₂)
, postopek prekinemo, ker ni rešitve.
Kako to deluje, si poglejmo na primerih.
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: