Gráfátíró rendszerek

Bevezetés

A gráfátíró rendszerek a lusta funkcionális programozási nyelvek egyik modellje. A modell még elég absztrakt, de a segítségével már jól megállapítható az egyes funkcionális programok memória- és időigénye.

A modellben a két fő fogalom az adatgráf és a gráfátírás.

Az adatgráf

Egy adatgráf:

Grw95fcb0edf77950b7271d384875915501.png

Az adatgráfok a következő tulajdonságokkal rendelkező gráfok:

Egy csúcs élei alatt a csúcs kimenő éleit fogjuk érteni.

Adatgráfok megvalósítása C-ben

Az adatgráfok a pointeres adatszerkezetek absztrakcióinak tekinthetők. Az előző adatgráf C-beli struktúrákkal megvalósítva:

Grwa7aae1771a004e6601c3a0e33788d228.png

A dobozok rekordokat jelölnek. A kis dobozok a rekordmezők. A szimbólumok valamilyen konkrét Int konstanst jelölnek, ami megkülönböztethetővé teszi őket. A nyilak pointereknek felelnek meg.

Funkcionális kifejezések adatgráffá konvertálása

Az adatgráfot szöveges formában is ábrázolhatjuk. Az adatgráf szöveges formája feltűnően fog hasonlítani a Clean vagy a Haskell nyelv egy résznyelvére, így ezt kiegészítve egy eljárást kaphatunk a funkcionális kifejezések adatgráffá konvertálására.

A szöveges formát példákon keresztül mutatjuk be.

Konstruktorokból felépített kifejezések

A konstruktorokból felépített kifejezések fa szerkezetű adatgráfoknak felelnek meg.

Számok és karakterek

Az elemi típusok, például az egész számok vagy a karakterek 0 argumentumú konstruktoroknak felelnek meg.

Példa:

'g'

Grw17522456409825f1fa81c517f856ded6.png

N-esek

Új szimbólumokat vezetünk be a párok, hármasok, négyesek stb. kezelésére: (,), (,,), (,,,), …

(14, 4.1)

Grw0155f59a2a5c593854ce06e8baa4168d.png

Listák

A listákat a (:) és [] konstruktorokkal írjuk le.

[71, 72]

Grw3e4d00263f82dacc9587f77d165a2db6.png

Egyéb algebrai adatszerkezetek

Minden konstruktornak egy szimbólum fog megfelelni. A konstruktor paraméterei a szimbólumot tartalmazó csúcs gyerekeinek felelnek meg.

-- data Maybe a = Just a | Nothing

Just 12

Grwb1790bdccbb0fff19e28e215d5a55a00.png

Megosztott részkifejezések

A megosztott kifejezéseket a Haskell és Clean programokban a where és let .. in kulcsszavak jelölik. (A let a where egymásbaágyazható megfelelője.)

(a, a) where a= 11

Grw1a2114d7c312da0f42e29117bfb179a4.png

Az előbbi gráf a számítás szempontjából ekvivalens a következővel:

(11, 11)

Grw7cc06c7ce8ebcbe9263f998cbf41ad3d.png

Az osztott kifejezések a gráfban a memóriahasználat és a futási idő szempontjából jelentősek.

Ciklikus gráfok

a  where a = 11: a
11: a  where a = 11: a

Grwe52ce96ceeca220054bd57f980ec1dea.png

Vegyük észre, hogy a kifejezések mint gráfok különbözőek, de szemantikusan azonosak, mert mindkettő azonos a következő gráffal:

Grwa536f6a4249a33f3b5cdc1f92e0759a3.png

Példa: Egy gyűrű leírása:

data R a = R a (R a) (R a)

r1 where
    r2 = R 92 r3 r1
    r3 = R 93 r4 r2
    r4 = R 94 r5 r3
    r5 = R 95 r6 r4
    r6 = R 96 r1 r5

Grw7f621d4237ecf53efa4f195202b8d002.png

Függvények az adatgráfban

A gráf csúcsaihoz rendelt szimbólumok kétfélék lehetnek:

Eddig csak konstruktor-szimbólumokat használtunk.

A függvényszimbólumok a gráfban elhalasztott számításokat jelölnek. A függvényszimbólumokat tartalmazó csúcsok gyerekei a függvény argumentumaira mutatnak.

61 + 62 * 63
f 11 12

Grw7f490cce87145ba26f7ea1ada36ee922.png

Figyeljük meg, hogy nincs szemantikus különbség a következő gráfok között:

[1, 2]

[1 * 1, 1 + 1]

[1] ++ [2]

sort [1, 2]

Tehát a függvényszimbólumokkal ugyanazt az adatot még többféleképpen leírhatjuk.

Aritás

Minden konstruktor- és függvényszimbólumhoz tartozik egy szám, ami megmondja, hogy az adott szimbólumhoz tartozó csúcsnak hány élének kell lennie. Ezt a számot a szimbólum aritásának nevezzük.

Példák aritásra:

Szimbólum Aritás
‘a’ 0
12.3 0
True 0
: 2
[] 0
+ 2
(,) 2
(,,) 3

Egy csúcsnak sohasem lehet több éle, mint a benne levő szimbólum aritása. Egyes esetekben viszont lehet kevesebb él, lásd a magasabbrendű függvényekkel foglalkozó részt.

A gráfredukció

A gráfredukció, vagy egyszerűen csak redukció az elhalasztott számításokat végzi el a gráfban. Egyetlen redukciós lépés mindig pontosan egy függvényszimbólumot eliminál a gráfból. A redukciós lépés során bekerülhetnek a gráfba újabb függvényszimbólumok is.

A redukció átírási szabályok alapján történik.

Átírási szabályok

Az átírási szabályok két részből állnak: egy bal és egy jobb oldalból. A bal oldal a minta, ezt illesztjük az adatgráfra, a jobb oldal pedig megmondja hogy hogyan kell módosítani az adatgráfot ha illeszkedik a minta.

Az átírási szabályokat szintén gráfokkal ábrázoljuk. Az átírási szabályoknak két gyökerük van, egy a bal oldal, egy a jobb oldal részére. Ezeket lhs és rhs-sel jelöljük (left hand side, right hand side).

Példa egy átírási szabályra:

False && True  =  False

Grw01558bdefc8fc1c120506c0a76fe6021.png

Az átírási szabályok tartalmazhatnak változókat. A változók tetszőleges részgráfot jelölnek. A változókat háromszöggel fogjuk jelölni.

Az előző átírási szabály általánosítása:

False && _  =  False

Grw4a47863a85c9e2366fc375b84252e6e7.png

Az átírási szabály jobb oldalán hivatkozhatunk a bal oldal részfáira:

True && a  =  a

Grwf799297941d9d10ba97431896b6ab2ce.png

A következő megszorításokat tesszük az átírási szabályokra:

Ezek a megszorítások biztosítják, hogy a gráfátíró rendszer konfluens legyen. A konfluencia azt jelenti, hogy ha egy adatgráfot kétféleképpen redukálhatunk, akkor nem számít, hogy melyik redukciót választjuk, mert később úgyis alkalmazható lesz a másik is, és így elérhetjük ugyanazt a közös végeredményt.

A gráfátírási lépések nem változtatják meg a kifejezések szemantikáját. Így például nincs szemantikus különbség az (1 + 2) * 5 és 3 * 5 között.

Az átírási szabályok szöveges alakja

Az átírási szabályok szöveges alakját a következőképpen képezzük:

Példa:

(h:t) ++ l = h: (t ++ l)

Grw066b6fad119199c19fe7f2869f31a991.png

Illesztés

Az illesztés során egy adott szabály bal oldalát illesztjük az adatgráf egy részgráfjához. Az illesztés sikeres, ha találunk egy olyan függvényt, ami a bal oldal csúcsaihoz hozzárendeli az adatgráf csúcsait a következőképpen:

A sikeres illesztés esetén a bal oldal képét redukálható részgráfnak, vagy redexnek nevezzük.

Az illesztés leírása a ciklikus gráfok miatt ilyen körülményes.

Példa: Legyen a gráf a következő:

Grw70462abcab580e40aa6e1b75e16c1562.png

Legyen az illesztendő bal oldal a következő:

Grw30b69c1e8b12afe2972e69a6bfb68f43.png

Az illesztés:

Grwcf8adc021e2a168247ff5cec56c9b0be.png

Másik példa: az f x where x= 11: x gráfra illesztjük az f (a: b: c) bal oldalt:

Grw18c19eb5ff41f7e080c4dc1cb9f2fc13.png

A gráfátírás

A grátátírás során az átírási szabály illeszkedő bal oldalt kicseréljük a jobb oldalra. Mivel lehetnek még egyéb hivatkozások is a bal oldalra a gráfban, ezért az átírást óvatosan kell végeznünk.

A gráfátírás a következő lépésekből áll:

  1. Illesztés: Választunk egy redexet (redukálható részgráfot) egy átírási szabállyal és egy illesztéssel.
  2. Másolás: Az átírási szabály jobb oldalát bemásoljuk az adatgráfba, úgy hogy a változók helyén az illesztés szerinti részgráfok szerepeljenek.
  3. Átirányítás: Minden a redex gyökerére mutató élt átirányítunk a bemásolt jobb oldal gyökerére.
  4. Szemétgyűjtés: Töröljük az adatgráf gyökeréből el nem érhető csúcsokat. A gyakorlatban ez a lépés elhalasztható addig, amíg el nem fogy a rendelkezésre álló memória.

Példa:

51: [52] ++ [53]

átírása a következőre:

51: 52: [] ++ [53]

Grwc4cc89b17742d9a1b781fdd9de9d72ee.png

A kék részek az átírás előttiek, a piros részek az átírás utániak.

Példa

Tekintsük a következő gráfátíró rendszert:

[] ++ l = l
(h:t) ++ l = h : (t ++ l)

cycle l = r  where r = l ++ r

Tekintsük a következő adatgráfot:

cycle [1, 2, 3]

Grw47afd73c5a326843037ae1a9f47e1235.png

Az első redukciós lépés hatása:

r where r = [1,2,3] ++ r

Grwa561b190a740cc10a01fe91108adf4e8.png

A második redukciós lépést részletesebben nézzük. Az második szabály illesztése és a jobb oldalának bemásolása után ezt látjuk (piros - bemásolt rész):

Grw34b9456922c35f46da4b2aad82079ac7.png

Az átirányítás után (szürke - régi élek, zöld - új élek):

Grw1bf1c0590c77f6dd04ce4a09e317e1c1.png

A szemétgyűjtés előtt (szürke - szemét):

Grw1e83e083215c068263643a32d56281dc.png

A szemétgyűjtés után (a második redukciós lépés végén):

Grw3dbd62f2a2956f27f2afc22bb87de11c.png

A harmadik redukciós lépés után:

Grwfbcec8ed6e01653f5c44607a619cfea4.png

A negyedik redukciós lépés után:

Grw3d6d9b93e538c37cc83973f1507ff484.png

Az ötödik redukciós lépés után:

Grw9b50bf80bab0a5379f350b0443620576.png

Megjegyzés: Ha a cycle szabálya helyett a következő (szemantikusan ekvivalens) redukciós szabályt alkalmaztuk volna

cycle l = l ++ cycle l

akkor egy végtelen gráf lenne a redukció végeredménye.

Magasabbrendű függvények

A megismert gráfátíró rendszerek kis változtatással alkalmassá tehetők magasabbrendű függvények kezelésére is.

Részleges alkalmazás

Először bevezetjük a részleges alkalmazás fogalmát. Az adatgráf egy csúcsa részleges alkalmazás, ha a csúcsnak kevesebb éle van, mint a benne levő szimbólum aritása.

Példa: A következő gráfban even részlegesen alkalmazott.

map even [1,2]

Grw81c521537c63628990427b62943adb1c.png

Megjegyzés: A konfluencia megtartása érdekében az átírási szabályok bal oldala nem tartalmazhat részleges alkalmazásokat.

Alkalmazás-csúcsok

A következő megoldandó probléma, hogy a részlegesen alkalmazott csúcsoknak hogyan adunk további éleket. A feladatot alkalmazás-csúcsok bevezetésével oldjuk meg.

Az alkalmazás csúcsokat $ szimbólummal jelöljük meg.

Példa:

even $ 1

Grw9ac2291bf1d586068660f6eb9aec4a4a.png

Az alkalmazás csúcsokra leginkább az átírási szabályokban van szükség. Például:

g f x = f x

Grw42124cbb628f58f3d46659138bce3e09.png

Alkalmazás-csúcsok eliminálása

Az alkalmazás-csúcsokat a következő szabályok szerint eliminálhatjuk:

f x1 $ y = f x1 y

f x1 x2 $ y = f x1 x2 y

f x1 x2 x3 $ y = f x1 x2 x3 y

...

(Ezek metaszabályok, mivel f helyén tetszőleges szimbólum állhat.)

Tehát az alkalmazás-csúcsok hozzáadnak egy élt az első argumentumukhoz, amely a második argumentumra mutat.

Példa: Legyen adott a következő átírási szabály:

g f x = f x    

Tekintsük a következő kifejezést:

g (f 33 34) 118

Az átírás végeredménye:

f 33 34 118

Grw9cc1942ffe63810888250f42694d818c.png

A kék részek az átírás előttiek, a zöld részek az átírás utániak, a piros részek az alkalmazás-csúcs eliminálása után keletkeztek.

Gráfátírási stratégiák

Ha nem tudjuk átírni az adatgráfot a gyökérnél, akkor a gráf gyökér normál formában van (ennek az analógja a fej normál forma a termátíró rendszerekben).

Ha nem tudjuk átírni az adatgráf egyetlen részgráfját sem (nincs redex), akkor a gráf normál formában van.

Ha a gráfban több redex is van, akkor dönthetünk, hogy melyiket írjuk át. A konfluens gráfátíró rendszereknél a döntés nem befolyásolja a végeredményt, de befolyásolhatja a végeredményhez vezető lépések számát.

Különböző redukciós stratégiákat dolgoztak ki, amelyek megmondják hogy egy adott adatgráf és adott gráfátírási szabályok esetén melyik szabályt kell alkalmazni, és hol kell a szabályt alkalmazni.

A redukciós stratégia normalizáló, ha minden adatgráfot ami normál formára hozható, normál formára is hoz.

Folytatás következik

Vissza a főoldalra

Főoldal