V matematiki poznamo zapis za funkcijski predpis:
x ↦ e
To preberemo “x se slika v e”, pri čemer je e neki izraz, ki lahko vsebuje x. Primer:
x ↦ x² + 3·x + 7
Običajni zapis funkcij
f(x) := x² + 3·x + 7
lahko obravnavamo kot okrajšavo za
f := (x ↦ x² + 3·x + 7)
Torej je funkcijski predpis bolj splošen kot imenovane funkcije.
Funkcijski predpis lahko uporabimo na argumentu. Na primer, f lahko uporabimo na 3 in dobimo izraz f(3), ki mu pravimo aplikacija.
Pravzaprav ni nobene potrebe, da funkcijski predpis poimenujemo f, lahko bi ga kar neposredno uporabljali in tvorili aplikacijo:
(x ↦ x² + 3·x + 7)(3)
To se morda zdi nenavadno, a je lahko koristno v programiranju (kot bomo videli kasneje). Nekateri programski jeziki imajo funkcijske predpise:
lambda x: x**2 + 3*x + 7\x -> x**2 + 3*x + 7fun x -> x*x + 3*x + 7(lambda (x) (+ (* x x) (* 3 x) 7))#² + 3*# + 7 & ali Function[x, x² + 3*x + 7]Vsi znamo računati s funkcijskimi predpisi in aplikacijami, čeprav se tega morda ne zavedamo. Računsko pravilo, ki se iz zgodovinski razlogov imenuje “β-redukcija”, pravi
(x ↦ e₁)(e₂) = e₁[x → e₂]
in ga preberemo:
Če uporabimo funkcijski predpis
x ↦ e₁na argumentue₂, dobimo izraze₁, v kateremxzamenjamo ze₂.
Zamenjavi spremenljivke x za neki izraz pravimo substitucija. Zgoraj smo ga zapisali [x → e₂].
Primer uporabe β-redukcije:
(x ↦ x² + 3·x + 7)(3) = 3² + 3·3 + 7
Pozoro: pravilo za funkcijski zapis ne trdi (x ↦ x² + 3·x + 7)(3) = 25, ampak samo, da lahko x zamenjamo s 3 in dobimo 3² + 3·3 + 7. Torej se pogovarjamo o računskih pravilih, ki so bolj osnovna kot računanje s števili!
V funkcijskem predpisu
x ↦ x² + 3·x + 7
se x imeuje vezana spremenljivka. S tem želimo povedati, da je x veljavna samo znotraj funkcijskega predpisa, je kot neke vrste lokalna spremenljivka. Če jo preimenujemo, se funkcijski zapis ne spremeni:
a ↦ a² + 3·a + 7
Poudarimo, da štejemo funkcijska predpisa za enaka, če se razlikujeta le po tem, kateri simbol je uporabljen za vezano spremenljivko.
V funkcijskem predpisu lahko nastopa tudi kaka dodatna spremenljivka, ki ni vezana. Pravimo ji prosta spremenljivka, na primer:
x ↦ a·x² + b·x + c
Tu so a, b in c proste spremenljivke. Teh ne smemo preimenovati, ker bi se pomen izraza spremenil, če bi to storili. (Pravzaprav imamo še proste spremenljivke ·, + in ²!)
Vezane in proste spremenljivke se pojavljajo tudi drugje v matematiki in računalništvu:
v integralu ∫ (x² + a·x) dx je x vezana spremenljivka, a je prosta
v vsoti ∑ᵢ i*(i-1) je i vezana spremenljivka
v limiti lim_{x → a} (x - a)/(x + a) je x vezana spremenljivka, a je prosta
v formuli ∃ x ∈ R . x³ = y je x vezana spremenljivka, y je prosta
v programu
for (int i = 0; i < 10; i++) {
s += i;
}
je i vezana spremenljivka, s je prosta.
v programu
if (false) {
int s = 0 ;
for (int i = 0; i < 10; i++) {
s += i;
}
}sta s in i vezani spremenljivki.
Kadar imamo proste in vezane spremenljivke, moramo paziti, da se prosta spremenljivka ne “ujame”, kar pomeni, da bi zaradi preimenovanja vezane spremenljivke prosta spremenljivka postala vezana. Na primer:
x ↦ a + x – “prištej a”y ↦ a + y – “prištej a”a ↦ a + a – “podvoji”Vidimo, da se je v tretjem primeru a “ujela”, ko smo x preimenovali v a. Mimogrede, treba je ločiti med tema dvema izrazoma:
y ↦ a + y – “prištej a”a ↦ a + y – “prištej y”Funkcijske predpise lahko gnezdimo, ali jih uporabljamo kot argumente. Primeri:
(x ↦ (y ↦ x · x + y))(42) = (y ↦ 42 · 42 + y)((x ↦ (y ↦ x · x + y))(42))(1) = (y ↦ 42 · 42 + y)(1) = 42 · 42 + 1(f ↦ f (f (3))) (n ↦ n · n + 1) = (n ↦ n · n + 1) ((n ↦ n · n + 1) (3)) = (n ↦ n · n + 1) (3 · 3 + 1) = (3 · 3 + 1) · (3 · 3 + 1) + 1Lahko se zgodi, da se zaradi vstavljanja enega funkcijskega predpisa v drugega kakšna vezana spremenljivka ujame. V takem primeru predhodno preimenujemo vezano spremenljivko. Primer:
(x ↦ (y ↦ x · y²)) (z + 1) = (y ↦ (z + 1) · y²)(x ↦ (y ↦ x · y²)) (y + 1) = (y ↦ (y + 1) · y²)(x ↦ (y ↦ x · y²)) (y + 1) = (x ↦ (a ↦ x · a²)) (y + 1) = (a ↦ (y + 1) · a²)Zapis x ↦ e postane dolgovezen, ko funkcijske zapise gnezdimo. Uporabili bomo λ-zapis:
λ x . e
To je prvotni zapis funkcijskih predisov, kot ga je zapisal Alonzo Church, vaš akademski praded! Temu zapisu pravimo abstrakcija izraza e glede na spremenljivko x.
Poleg tega bomo aplikacijo f(x) pisali brez oklepajev f x. Seveda pa oklepaje dodamo, kadar bi lahko prišlo do zmede. Dogovorimo se, da je aplikacija levo asociativna, torej
e₁ e₂ e₃ = (e₁ e₂) e₃
V abstrakciji λ vedno veže največ, kolikor lahko. Torej je λ x . e₁ e₂ e₃ je enako λ x . (e₁ e₂ e₃) in ni enako (λ x . e₁) e₂ e₃.
Kadar imamo gnezdene abstrakcije
λ x . λ y . λ z . e
to pomeni λ x . (λ y . (λ z . e)). Dogovorimo se še, da lahko tako gnezedeno abstrakcijo krajše zapišemo
λ x y z . e
Pravilo za računanje lahko uporabimo na različne načine. Primer:
(λ x . (λ f . f x) (λ y . y)) ((λ z . g z) u)
je enak
(λ x . (λ f . f x) (λ y . y)) (g u)
in prav tako
(λ x . (λ y . y) x) ((λ z . g z) u)
Vendar pa velja lastnost konfluence, ki pravi, da vrstni red računanja ni pomemben. Natančneje, če ima e dva možna računska koraka, e ↦ e₁ in e ↦ e₂, potem lahko v e₁ in v e₂ izvedemo take računske korake, da se bosta pretvorila v isti izraz.
V zgornjem primeru:
(λ x . (λ f . f x) (λ y . y)) (g u) =
(λ x . (λ y . y) x) (g u) =
(λ x . x) (g u) =
g u
in
(λ x . (λ y . y) x) ((λ z . g z) u) =
(λ x . x) ((λ z . g z) u) =
(λ z . g z) u =
g u
Dobili smo izraz, v katerem ne moremo več narediti računskega koraka. Pravimo, da je tak izraz v normalni obliki.
Postavi se vprašanje, kako sistematično računati. Poznamo nekaj strategij:
Neučakana (eager evaluation): v izrazu e₁ e₂ najprej do konca izračunamo e₁ da dobimo λ x . e, nato do konca izračunamo e₂, da dobimo e2' in šele nato vstavimo e₂' v e.
Lena (lazy evaluation): v izrazu e₁ e₂ najprej izračunamo e₁, da dobimo λ x . e, nato pa takoj vstavimo e₂ v e.
Poleg tega lahko računamo znotraj abstrakcij ali ne. Programski jeziki znotraj abstrakcij ne računajo (to bi pomenilo, da se računa telo funkcije, še preden smo funkcijo poklicali).
λ-račun je splošen programski jezik, ki je po moči ekvivalenten Turingovim strojem. Ogledamo si nekaj primerov.
id := λ x . x
compose := λ f g x . f (g x)
const := λ c x . c
true := λ x y . x
false := λ x y . y
if := λ b t e . b t e
pair := λ a b . λp . p a b ;
first := λ p . p (λx y . x) ;
second := λ p . p (λx y. y) ;
Še več primerov je na voljo v primeri.lambda, kjer je uporabljena sintaksa programskega jezika lambda iz PL Zoo, programski jezik lambda. Na voljo je tudi spletni vmesnik za lambda (kogar zanima, kako se to naredi, si lahko ogleda repozitorij repl-in-browser).