Previous Up Next

Peatükk 9 Andmestruktuurid puhtas λ-arvutuses

Eelnevalt nägime, kuidas arvutada konstantidega λ-arvutuses, kus konstantideks võtsime tõeväärtused, täisarvud, paarid ja listid. Nüüd vaatame, kuidas defineerida tõeväärtused, naturaalarvud, paarid ja listid kasutades puhast λ-arvutust. See tähendab, et teoreetilises lähenemises võime konstante ja δ-reduktsioone ignoreerida.

Definitsioonideks valime kombinaatorid: ilma vabade muutujateta termid, mis on normaalkujul. Nii käituvad meie definitsioonid sarnaselt konstantidele — ei tekita juurde vabu muutujaid ega redutseeru selliselt, et me neid ära ei tunne. Tegelikult on võimalik kõik abstraktsioonid kirjeldada S, K ja I kombinaatoritega.

I := λ x. x          
K := λ xy. x          
S := λ fgx. fx (gx)          

Tõeväärtuste spetsifikatsiooniks võtame tõeväärtuste funktsioonide δ-reduktsioonid, leiame konstantidele vastavad λ-termid ja näitame, et δ-reduktsiooni asemel saame teha β-reduktsioone ja saada sama tulemuse.

9.1 Tõeväärtuste implementatsioon

Tõeväärtuste operatsioonid on spetsifitseeritud järgnevalt.

nottrueδ false          
notfalseδ true          
condtruee1e2δ e1          
condfalsee1e2δ e2          

Sobivate definitsioonide leidmiseks kasutame järgnevat strateegiat: väärtuseks võtame vastava väärtuse mustrisobituse operatsiooni. Tõeväärtuse true mustrisobitus ehk tingimuslause võtab kaks parameetrit, e1 ja e2, ning tagastab e1. Seega võtame true := λ xy.  x. Sarnase argumentatsiooniga võtame false := λ xy.  y. Muud operatsioonid saame programmeerida kasutades teadmist, et tõeväärtused on nendele vastavad tingimuslaused.

not:= λ t. tfalsetrue          
cond:= λ t.  t          

Saame näidata, et δ-reduktsioonile vastab mitmesammuline β-reduktsioon.

nottrue=t. tfalsetrue) true
 βtruefalsetrue
 =x. λ y. x) falsetrue
 βy. false) true
 βfalse
notfalse=t. tfalsetrue) false
 βfalsefalsetrue
 =x. λ y. y) falsetrue
 βy. y) true
 βtrue
condtruee1e2=t. t) truee1e2
 βtruee1e2
 =x. λ y. x) e1e2
 βy. e1) e2
 βe1
condfalsee1e2=t. t) falsee1e2
 βfalsee1e2
 =x. λ y. y) e1e2
 βy. y) e2
 βe2

9.2 Paaride implementatsioon

Paaride operatsioonid on spetsifitseeritud järgnevalt.

fst  (paire1e2) →δ e1           
snd  (paire1e2) →δ e2           

Definitsioonide leidmise strateegia on sama mis tõeväärtuste puhul — leiame mustrisobitusele vastava termi. Idrises saab paaride mustrisobitust teostada avaldisega match MkPair e_1 e_2 with MkPair x y => p. Seega λ-arvutuses võime võtta näiteks pair = λ e1e2p. pe1e2. Ehk siis paari komponendid antakse ainukesele harule edasi eraldi parameetritena. Siis fst ja snd saame impmeneteerida kasutades mustrisobitus järgnevalt.

fst:= λ p. ptrue          
snd:= λ p. pfalse          

Nüüd saame näidata definitsioonide vastavust spetsifikatsioonile.

fst  (paire1e2)=p. ptrue) (paire1e2)
 β(paire1e2) true
 =e1e2p. pe1e2) e1e2true
 βtruee1e2
 =xy. x) e1e2
 βe1
snd  (paire1e2)=p. pfalse) (paire1e2)
 β(paire1e2) false
 =e1e2p. pe1e2) e1e2false
 βfalsee1e2
 =xy. y) e1e2
 βe2

9.3 Naturaalarvud

Natutaalarvude kohta nõuame, et succ , pred , iszero , add ja mul arvutaks vastavalt järgmise arvu, eelmise arvu, arvu võrdumise nulliga, liitmise ja korrutamise.

Esmalt püüame ehitada nii, et nulli esitab identsusfunktsioon λ x. x ja nullist suuremad arve paarid, mille esimene komponent on false ja teine on ühe võrra väiksem number. Nii on arvude struktuur sarnane Idrise struktuurile aga lisatud on false komponendid. Sellist konstruktsiooni nimetatakse standardnumbriteks.

⌜ 0 ⌝ := λ x. x          
n+1 ⌝ := pairfalse  ⌜ n         
succ := λ n. pairfalsen          
pred := snd          
iszero := fst          

Saame näidata, et succ , pred ja iszero töötavad korrektselt. Näeme, et pred töötab vaid nullist suuremate arvude korral.

succ  ⌜ n=n. pairfalsen) ⌜ n
 βpairfalse  ⌜ n
 =n+1 ⌝
pred  ⌜ n+1 ⌝=p. pfalse) (pairfalse  ⌜ n ⌝ )
 βpairfalse  ⌜ n ⌝ false
 =e1e2p. pe1e2) false  ⌜ n ⌝ false
 βfalsefalse  ⌜ n
 =(λ xy.  y) false  ⌜ n
 βn
pred  ⌜ 0 ⌝=p. pfalse) (λ x. x)
 βx. x) false
 βfalse
iszero  ⌜ 0 ⌝=p. ptrue) (λ x. x)
 βx. x) true
 βtrue
iszero  ⌜ n+1 ⌝=p. ptrue) (pairfalse  ⌜ n ⌝)
 βpairfalse  ⌜ ntrue
 =e1e2p. pe1e2) false  ⌜ ntrue
 βtruefalse  ⌜ n
 βfalse

Kui makrode defineerimisel oleks rekursioon lubatud, saaks standardnumbrite liitmise defineerida kui add := λ xy. cond  (iszerox) y (add  (predx)(succy)). Hiljem, peatükis 9.5, näeme ühte võimalust, kuidas rekursiooniga hakkama saada. Enne aga vaatame ühte alternatiivset lähenemist — Churchi numbreid.

Numbrite defineerimiseks kasutame funktsiooni termide astendamise notatsiooni. Termide E, E∈ Λ jaoks EnE′ rakendab funktsiooni E n-korda, alustades argumendist E′. Ehk E0E′ = E′, E1E′ = EE′, E2E′ = E (EE′) , E3E′ = E (E (EE′)) jne. Nüüd saame defineerida Churchi numbrid.

n
:= λ fx. fnx          
succ := λ n. λ fx. f (nfx)          
iszero := λ n. n (λ x. false) true          

Churchi numbrite intuitsioon on, et arv n tähistab mingi valitud operatsiooni n-kordset tegemist — arv vastab sellele, mitu iteratsiooni tuleb teha. Arvule ühe lisamine tähendab ühe iteratsiooni lisamist. Nulliga võrdumist saab kontrollida nii, et algseisuks võetakse true ja tsüklis muudetakse see false -ks; nulli iteratsiooni puhul jääb seisundiks true ja rohkemate iteratsioonide puhul on tulemus false . Vaata formaalseid β-reduktsioone.

succ
n
=n. λ fx. f (nfx)) (λ fx. fnx)
 βλ fx. f ((λ fx. fnx) fx)
 βλ fx. f (fnx)
 =λ fx. fn+1x
 =
n+1
iszero
0
=n. n (λ x. false) true) (λ fx. x)
 βfx. x) (λ x. false) true
 βtrue
iszero
n+1
=n. n (λ x. false) true) (λ fx. f (fnx))
 βfx. f (fnx)) (λ x. false) true
 βx. false) ((λ x. false)ntrue)
 βfalse

Liitmise, korrutamise ja isegi astendamise saame defineerida järgnevalt. Pane tähele, et astendamise definitsiooni saaks kirjutada veel lühemalt: exp = λ mn. nm.

add := λ mn. λ fx. mf (nfx)          
mul := λ mn. λ fx. m (nf) x          
exp := λ mn. λ fx. nmfx          

Vaatame, kuidas term addmn β-redutseerub naturaalarvude m ja n summaks. Intuitiivselt, kõigepealt tehakse n iteratsiooni ja seejärel veel m iteratsiooni. Korrutamise korrektsuse verifitseerimiseks peame veenduma, et (λ x. fnx )mx on tõesti sama kui fmnx. Ehk, intuitiivselt m korda tehakse n iteratsiooni.

add
m
n
=
mn. λ fx. mf (nfx))  
m
n
 β
λ fx. 
m
f (
n
fx)
 =λ fx. (λ fx. fmx) f ((λ fx. fnx) fx)
 βλ fx. fm (fnx)
 =
m+n
mul
m
n
=
mn. λ fx. m (nf) x)  
m
n
 β
λ fx. 
m
 (
n
f) x
 =
λ fx. (λ fx. fmx ) (
n
f) x
 β
λ fx. (
n
f)mx
 =λ fx. ((λ fx. fnx ) f)mx
 βλ fx. (λ x. fnx )mx
 βλ fx. fmnx
 =
mn

Churchi numbrite puhul on keeruliseks ülesandeks hoopis ühe lahutamine. Intuitiivselt, kui meil on arv n ja me saame teha n iteratsiooni aga soovime teha n−1 iteratsiooni. Selle probleemile on kaks standardset lahendust. Esiteks vaatame lahendust, kus meil intuitiivselt on iteratsioonis lisamuutuja — tõeväärtus, mis ütleb, kas me oleme juba ühe maha lahutanud. Vajame abifunktsiooni, mis käituks järgnevalt.

prefnf (true,x)(false,x)
prefnf (false,x)(false,fx)
(prefnf)n (false,x)(false,fnx)

Sellist spetsifikatsiooni rahuldab järgmine definitsioon.

prefn := λ fp. (false,(cond  (fstp) (sndp) (f (sndp))))           
prefnf (true,x)=fp. (false,(cond  (fstp) (sndp) (f (sndp))))) f (true,x)
 β(false,(cond  (fst  (true,x)) (snd  (true,x)) (f (snd  (true,x)))))
 β(false,(condtrue  (snd  (true,x)) (f (snd  (true,x)))))
 β(false,snd  (true,x))
 β(false, x)
prefnf (false,x)=fp. (false,(cond  (fstp) (sndp) (f (sndp))))) f (false,x)
 β(false,(cond  (fst  (false,x)) (snd  (false,x)) (f (snd  (false,x)))))
 β(false,(condfalse  (snd  (false,x)) (f (snd  (false,x)))))
 β(false, f (snd  (false,x)))
 β(false, fx)

Reduktsiooni (prefnf)n (false,x) ↠ (false,fnx) tõestame induktsiooniga üle naturaalarvu n. Vaatame baasjuhtu n=0 ja induktsiooni sammu n=n′+1.

(prefnf)0 (false,x)=(false,x)
 =(false, f0x)
(prefnf)n′+1 (false,x)=(prefnf)n (prefnf (false,x))
 β(prefnf)n (false, fx)
 β(false, fn (fx))
 =(false, fn′+1x)

Siis saame defineerida eelmise arvu leidmise operaatori pred .

pred := λ n. λ fx. snd  (n (prefnf) (true,x))           
pred
0
=
n. λ fx. snd  (n (prefnf) (true,x))) 
0
 β
λ fx. snd  (
0
 (prefnf) (true,x))
 =λ fx. snd  ((λ fx. f0x) (prefnf) (true,x))
 βλ fx. snd  (true,x)
 βλ fx.  x
 =λ fx. f0x
pred
n+1
=
n. λ fx. snd  (n (prefnf) (true,x))) 
n+1
 β
λ fx. snd  (
n+1
 (prefnf) (true,x))
 =λ fx. snd  ((λ fx. fn+1x) (prefnf) (true,x))
 βλ fx. snd  ((prefnf)n+1 (true,x))
 =λ fx. snd  ((prefnf)n (prefnf (true,x)))
 βλ fx. snd  ((prefnf)n (false,x))
 βλ fx. snd  (false,fnx))
 βλ fx. fnx
 =λ fx. fnx

Seega oleme andnud ühe naturaalarvude implementatsiooni kasutades paari ja tõeväärtust. Teine variant on implementeerida viivitus naturaalarvude paariga, mille mõlemad komponendid on alguses nullid. Iga iteratsioon kopeerib paari teise komponendi esimeseks ja suurendab teiset komponenti ühe võrra — nii on paari esimene komponent alati peale esimest iteratsiooni ühe võrra väiksem. Peale iteratsioone tagastatakse paari esimene komponent.

f:= λ p. pair  (sndp) (succ  (sndp))         
pred
:= λ n. fst  (nf (pair
0
0
))
         

Esmalt tõestamie induktsiooniga, et f-i n-kordsel rakendamisel paarile pairmm+1 on tulemuseks (pairm+nm+n+1). Induktsiooni baas kehtib triviaalselt kuna f0 (pairmm+1) = pairmm+1. Induktsiooni sammu kehtivust tõestab järgnev reduktsioon.

fn+1
 (pair
m
m+1
) =
         
 
= (λ p. pair  (sndp) (succ  (sndp))) (fn (pair
m
m+1
))
         
 
β pair  (snd  (fn (pair
m
m+1
))) (succ  (snd  (fn (pair
m
m+1
))))
         
 
β pair  (snd  (pair
m+n
m+n+1
)) (succ  (snd (pair
m+n
m+n+1
)))
         
 
β pair
m+n+1
 (succ
m+n+1
)
         
 
β pair
m+n+1
m+n+1+1
         

Nüüd saame hõlpsasti näidata, et pred0 redutseerub väärtuseks 0 ja predn+1 redutseerub väärtuseks n.

pred
0
=
n. fst  (nf (pair
0
0
))) 
0
 β
fst  (
0
f (pair
0
0
))
 β
fst  (pair
0
0
)
 β
0
pred
n+1
=
n. fst  (nf (pair
0
0
))) 
n+1
 β
fst  (
n+1
f (pair
0
0
))
 β
fst  (fn (f (pair
0
0
)))
 β
fst  (fn (pair
0
1
))
 β
fst  (pair
n
n+1
)
 β
n

9.4 Listid

Listide kohta on jägnevad δ-reduktsioonireegild.

nullnilδ true          
null (conse1e2)δ false          
hd (conse1e2)δ e1          
tlnilδ nil          
tl (conse1e2)δ e2          

Sarnaselt naturaalarvudele, on siin võimalik kaks lähenemist. Esiteks võime lähtuda listi mingist fold-funktsioonist ja implementeerida null, hd ja tl kasutades seda fold-i. Teine võimalus oleks kasutada paare ja tõeväärtueseid — teades ette, et fold-i puudumisel on paljude funktsioonide implementeerimiseks vaja rekursioon.

Vaatame esmalt põgusalt listide implementatsiooni, mis on tuletatud listide foldr funktsioonist.

nil:= λ fb. b          
cons:= λ xl. λ fb. fx (lfb)          

Nüüd saame implementeerida konstandid null ja hd.

null:= λ l. l (λ yz. false) true          
hd:= λ l. l (λ yz. y) I          
nullnil=l. l (λ yz. false) true) nil
 βnil (λ yz. false) true
 =fb. b) (λ yz. false) true
 βtrue
null (consxl)=l. l (λ yz. false) true) (consxl)
 βconsxl (λ yz. false) true
 =xl. λ fb. fx (lfb)) xl (λ yz. false) true
 β (λ yz. false) x (l (λ yz. false) true)
 βfalse
hd (consxl)=l. l (λ yz. y) I) (consxl)
 βconsxl (λ yz. y) I
 =xl. λ fb. fx (lfb)) xl (λ yz. y) I
 βyz. y) x (l (λ yz. y) I)
 βx

Listi saba leidmine kasutades fold funktsioone on võimalik aga parajalt tülikas. Definitsioonid tulevad pikad ja korrektsuse näitamine võtab palju samme. Seetõttu piirdume siin Idrise koodiga, mis arvutavad listi saba kasutade foldr või foldl.

1tl1 : List a -> List a 2tl1 l = snd (foldl f (True,[]) l) where 3 f : (Bool, List a) -> a -> (Bool, List a) 4 f (True ,r) x = (False, r) 5 f (False,r) x = (False, r++[x]) 6 7tl2 : List a -> List a 8tl2 l = snd (foldr f (pred (length l), []) l) where 9 f : a -> (Nat, List a) -> (Nat, List a) 10 f x (0, r) = (0, r) 11 f x (S n, r) = (n, x::r)

Teine võimalus listifunktsioone defineerida on läbi paaride ja tõeväärtuste.

nil:= λ z. z          
cons:= λ xy. pair false  (pairxy)          
null:= λ z. ztrue          
hd:= λ z. fst  (sndz)         
tl:= λ z. snd  (sndz )          

Nüüd saame näidata, et meie definitsioonid vastavad spetsifikatsioonile kasutades β-reduktsiooni.

nullnil=z. ztrue) nil
 βniltrue
 =z. z) true
 βtrue
null (consxl)=z. ztrue ) (consxl)
 βconsxltrue
 =xy. pair false  (pairxy)) xltrue
 βpair false  (pairxl) true
 βtrue false  (pairxl)
 βfalse
hd (consxl)=z. fst  (sndz)) (consxl)
 βfst  (snd  (consxl))
 =fst  (snd  ((λ xy. pair false  (pairxy)) xl))
 βfst  (snd  (pair false  (pairxl)))
 βfst  (pairxl)
 βx
tl (consxl)=z. snd  (sndz)) (consxl)
 βsnd  (snd  (consxl))
 =snd  (snd  ((λ xy. pair false  (pairxy)) xl))
 βsnd  (snd  (pair false  (pairxl)))
 βsnd  (pairxl)
 βl

Sellised definitsioonid ei võimalda meil aga defineerida rekursiivset foldr funktsiooni. Järgmisena vaatamegi, kuidas toetada rekursiivset käitumist.

9.5 Püsipunktid

Termi M nimetatakse püsipunktikombinaatoriks kui kehtib

F.  MF  =βF (MF).

Tuntuim püsipunktikombinaator on Haskell Curry “paradoksaalne” kombinaator

Y := λf. (λx. f (x x)) (λx. f (x x)).

Saame näidata, et Y on püsipunktikombinaator.

Ye
β
x. e (x x)) (λx. e (x x))
         
 β e ((λx. e (x x)) (λx. e (x x)))         
 
βe
(Y e)
         

Pane tähele, et tõestuse viimase sammuna tuleb β-ekspansioon, mitte β-reduktsioon. Seetõttu on mõnevõrra raskendatud Y e-ks ekspandeerutava termi äratundmine suurema termi sees — kui meil oleks mingi konkreetne term e. Seetõttu võib olla kasulik kasutada hoopis “tugevat” püsipunkti kombinaatorit Θ.

Θ := (λ xy. y (xxy)) (λ xy. y (xxy))

Kombinaatori Θ kohta saame tõestada tugevama väite, et Θ e  →β  e (Θ e).

Θ e
β
x y. y (x x y)) (λx y. y (x x y))
e
         
 
β
y. y ((λx y. y (x x y)) (λx y. y (x x y)) y)) e
         
 βe ((λx y. y (x x y)) (λx y. y (x x y)) e)         
   =   ee)          

Püsipunktikombinaatoreid saab kasutada rekursiivsete funktsioonide defineerimiseks. Võtame näiteks standardnumbrite (naturaalarvude) liitmise. Rekursiivse väljakutse jaoks lisame definitsiooni esimese parameetri ja rakendame funktsiooni püsipunktikombinaatorile.

add := Yf x y. cond (iszero x) y (f (pred x)(succ y)))

Tõestame näiteks ka liitmise korrektsust, s.t, et add m n redutseerub avaldiseks m+n. Tõestuseks kasutame induktsiooni ja alustame baasjuhust m=0, kus kasutame add-i definitsiooni järel püsipunktikombinaatori võrdust Y f =β f (Y f), teadmist iszero 0β true ja cond true e1 e2β e1.

add
0
n
= Yf x y. cond (iszero x) y (f (pred x)(succ y)))
0
n
         
 
=β λf x y. cond (iszero x) y (f (pred x) (succ y))) add
0
n
         
 
β cond (iszero
0
)
n
(add (pred
0
) (succ
n
))
         
 
β cond true
n
(add (pred
0
) (succ
n
))
         
 
β
n
         

Jätkame induktsiooni sammu m=m′+1 tõestusega, kus kasutame add-i definitsiooni järel püsipunktikombinaatori võrdust Y f =β f (Y f), teadmist iszero m+1β false , teadmist pred m+1β m, teadmist succ nβ n+1, teadmist cond false e1 e2β e2 ning induktsiooni hüpoteesi ∀ k, add m kβ m+k.

add
m′+1
n
= Yf x y. cond (iszero x) y (f (pred x)(succ y)))
m′+1
n
         
 
=β λf x y. cond (iszero x) y (f (pred x) (succ y)) add
m′+1
n
         
 
β cond (iszero
m′+1
) y (add (pred
m
) (succ
n
))
         
 
β add (pred
m′+1
) (succ
n
)
         
 
β add
m
n+1
         
 
β
m′+n+1
=
(m′+1)+n
         

Sarnaselt liitmisoperatsioonile add saame kõik rekursiivsed definitsioonid kirjutada mitterekursiivselt, kasutades mõnda püsipunktioperaatorit. Üldine muster on, et funktsioonile tuleb lisada esimene parameeter ja sellega asendada kõik rekursiivsed väljakutsed. Nii saame funktsiooni, mis töötab soovitud viisil kuni rekursiivse väljakutseni. Kuidas aga anda funktsioonile see esimene argument? Seda teeb püsipunktioperaator. Ehk kui soovime defineerida funktsiooni, mis käitub nagu võrdus f = λx y z. … (f a b c) … siis piisab meil see defineerida mitterekursiivse makrona f := Y (λf’ x y z. … (f’ a b c) …).


Previous Up Next