Gondolatok
a típus-specifikációk kompatibilitásának
vizsgálatáról

Eszmefuttatásom célja egy módszert vázolni, amely perspektivikus lehet annak vizsgálatában, hogy a típusok, típusosztályok algebrai, valamint a „hagyomá­nyosabb” külső felület szerinti specifikációjának[1] egymáshoz illeszkedése nem sérül-e. A módszer egyik sarkalatos pontja a Prolog nyelv felhasználása, amely –elképzeléseim szerint– képes támogatni eme vizsgálatot. Elvárásom, hogy egy alkalmasan felépített Prolog nyelvű programmal igazolható, a típus két leírásá­nak kompatibilitása: logikai egymáshoz illeszkedősége.

Egy ilyen módszer kidolgozásának elsősorban gyakorlati jelentősége van. Is­mert [KV2001] ugyanis, hogy algebrai specifikációból formálisan is előállít­hatók az érintett operációk elő- és utófeltételes specifikációi, tehát a típus­osztály P/P-specifikációja. A programfejlesztés során a programozó nem az említett tétel alkalmazásával (még ha tud is róla), hanem inkább „józanésszel” jut el a típusosztály-megvalósításhoz közelebb álló (azaz egyéb operá­cióktól függetlenül megfogalmazott) másik specifikációjához. Ekkor nagyobb „űr” tátong a kétféle specifikáció között, ami fölveti a kompatibilitás meglété­nek kérdését.

Egy ilyen módszer más szempontból is hasznos lehet. Ha ugyanis valóban „me­chanikussá” sikerül tenni a kompatibilitás vizsgálatát, jó eséllyel akár prog­rammal is elvégezhetővé válik az ellenőrzés.

I. rész
Alapok


Vázlat:

1. A kétféle specifikációról 3

1.1. Algebrai specifikációs nyelv. 3

1.1.1. A típus algebrai leírásának kerete. 3

1.1.2. Implicit error-axiómák. 4

1.2. A külső felület szerinti specifikáció. 5

1.2.1. A típus P/P-specifikáció kerete: a modul 5

1.2.2. P/P-specifikációbeli formalizmus: a sorozatnyelv. 6

1.2.3. A P/P-specifikáció algebraiba beilleszthetőségének megsejtése. 7

2. Prolog nyelvi minimum.. 9

3. Átírási szabályok. 11

3.1. Algebrai és P/P-specifikáció mint egyetlen szabály-gyűjtemény. 11

3.2. Az operátor-szabály szignatúrája. 11

3.3. Az algebrai leírás szabályokká alakítása. 12

3.3.1. Implicit error-axiómák Prolog szabályai 13

3.3.2. Iniciális error-axióma szerepe. 13

3.3.3. Algebrai axióma Prolog szabálya. 13

3.3.4. P/P-specifikáció Prolog szabálya. 15

3.4. Az implikáció kódolása. 16

3.5. A szabályok programmá egyesítése. 17

4. A puding próbája. 21

5. Összefoglalás. 26

4.1. Az operációk szabályalakja (Domains/Predicates) 26

4.2. Az operátorok P/P-specifikációjának szabálymegfelelője (Clauses) 26

4.3. Az algebrai axiómák mint szabályok (Predicates/Clauses) 27

4.4. Az összeillés vizsgálatának kezdeményezése (Goal) 28

Irodalom.. 29

Programhivatkozások. 30

Függelék. 31

A. Sorozat betét 31

B. Módosított Prolog program.. 31

 


1. A kétféle specifikációról

Típusok (típuskonstrukciók), típusosztályok leírását gyakorta többféle absztrakciós szin­ten is elvégezzük [FH2002, SzPG2002, PSzZs1998]: haladva a legabsztraktabb (algebrai) felöl, a konkré­tabb, külső felülettel (P/P-, export modullal) specifikálton át a megvalósítás apró részleteit is tartalmazó (modul-specifikáció vagy esetleg már konkrét programozási nyelven megírt) felé. Fontos tudnunk, hogy ezen inkrementális programfejlesztés közben megtett lépéseink korrektek abban az értelemben, hogy ugyanazt a típust írják le mindannyian eltérő nyelvi sza­bályok között (értsd: formalizmus mellett), eltérő „részletességgel”.

Az elkövetkezőkben villanásszerűen utalunk a két, számunkra most fontos specifikációs „nyelvezetre”: az algebrai [GN1986] és a P/P-specifikációra [SzZs2000], hogy azután vizs­gálhassuk a Prolog programban történő egyesítésük lehetőségét. [SzP1999]

1.1. Algebrai specifikációs nyelv

1.1.1. A típus algebrai leírásának kerete

A típusosztály legfelsőbb szintű megragadását jelenti az algebrai leírás, amely „csak” annyit igyekszik lerögzíteni, hogy a típushoz asszociált műveletek jellegzetes kom­binációi milyen, a tesztelő operációkkal érzékelhető változásokat eredményeznek a közép­pontba helyezett adat­szerkezeten. Az algebrai specifikáció csupán axiómákat állít föl. Tehát az adatábrázolástól éppúgy távol marad, mint az algoritmikus megvalósítástól.

Algebrai specifikációhoz számos „nyelvezet” létezik. (Pl. [KV2001]) Az alábbi táblázatba annak az algebrai leírásnak a szintaktikai minimumát foglaltuk, amelyet az ELTE Informatika tanárszakán használunk.

Szintaktikai „minta”

Magyarázat

TípusKonstrukció(paraméterek):

Asszociált műveletek:

Operáció(Értelmezési tartomány): Értékkészlet

A definiálandó típuskon­strukció „alkalmazási sablona” (feje).

Operációinak szigna­túrája (értelmezési tar­tománya, értékkészlete)

Axiómák:

Jelölések:

·       objektum: Típus(paraméterek) = értelmező megjegyzés

·      

1oaz axióma informális szövege vagy megnevezése...

        a formalizált axióma

2o

 

Az operációk kapcsolatát leíró axiómák, és a bennük fölhasznált objektumok „deklarációi”.

Esetleg kibővítve fontos következményállítások­kal.

1.1.2. Implicit error-axiómák

Mivel a specifikálás során foglalkozni kell az operációk (nem kizárható) illegális használa­tával, annak körülményeivel, be kell vezetni egy „univerzális” NemDef értéket.[2] Egy ope­rációt „eredménnyel” (más szóval: legálisan) nem használhatunk egy NemDef értékű (azaz egy hiba-állapotba jutott) adatra. Ezt mondja ki az error-axióma. Ezt az axiómát a további­akban nem szerepeltetjük explicite az egyes operációkkal kapcsolatban, ezért minősítjük implicitnek.

Az adatok létrehozás előtti állapota is a NemDef. (Iniciális error-axióma)

Dolgozatunkban egy közismert példát követünk végig: a verem típuskonstrukció (közismer­tebb néven: verem-osztály) példáját. Lássuk a verem algebrai leírását!

Példa:

 

Verem(Típus Elem):

Asszociált műveletek:

Üres: Verem

Üres?(Verem): Logikai

Tele?(Verem): Logikai

Tető(Verem): Elem È {NemDef}

Verembe(Verem,Elem): Verem È {NemDef}

Veremből(Verem): ( Verem x Elem ) È {NemDef}

Axiómák:

Jelölések:

·  v,v': Verem(Elem) = veremtípusú objektum

·  e,e': Elem = elemtípusú objektum

1o  Üres – axióma

            Üres?(Üres)

2o  Üres? – axióma

            Ø Tele?(v)  Þ  Ø Üres?(Verembe(v,e))

3o  Tető–Verembe – axióma

            Ø Tele?(v)  Þ  Tető(Verembe(v,e))=e

3’o  Tető – hiba-axióma

            Üres?(v) Þ  Tető(v)=NemDef

4o  Veremből – axióma

            Ø Tele?(v)  Þ  Veremből(Verembe(v,e))=(v,e)  Ù
Ø Üres?(v)  Þ  Verembe(Veremből(v))=v

4’o  Veremből – hiba-axióma

            Üres?(v) Þ  Veremből(v)=NemDef

1.2. A külső felület szerinti specifikáció

1.2.1. A típus P/P-specifikáció kerete: a modul

Az algebrai specifikációhoz képest fontos eltérés, hogy amíg az algebrai elvárásaink egyetlen összefüggő rendszert (axiómarendszert) alkotnak, addig a külső felület szerinti specifikáció ese­tében minden egyes operáció egymástól független, önálló „egységet”, mikrórendszert alkot.

ExportModul TípusNév(InputParaméterek)[3]

   Konstans   [Ritkán van ilyenre szükség.]
          Kons1,Kons2,...      [a TípusNév típus néhány kiemelendő
                                konstansa]
   Függvény  Fv1(FormParam): FvTip1
          Ef: az Fv1 alkalmazhatóságának feltétele
          Uf: az Fv1 által elvégzett transzformáció
                ...

   Eljárás    Elj1(FormParam)
          Ef: az Elj1 alkalmazhatóságának feltétele
          Uf: az Elj1 által elvégzett transzformáció
                ...

   Operátor Op1(FormParam): OpTip1
          Ef: az Op1 alkalmazhatóságának feltétele
          Uf: az Op1 által elvégzett transzformáció
                ...

Modul vége.

Az operációk közötti kapcsolatot a típust meghatározó adatok jelentik. Ezen adatokat transz­formálják, elemzik az egyes operációk. Itt tehát már nem kerülhető el, amit eddig ügyesen figyelmen kívül hagytunk: az ábrázolás. Törekszünk természetesen még mindig kellően ál­talánosan fogalmazni.

1.2.2. P/P-specifikációbeli formalizmus: a sorozatnyelv

Az általunk vizsgált típusok (osztályok) mindegyikénél egy sorozat játszik majd középponti szerepet. Ezért alkalmazunk az elő- és utófeltételben egy „sorozatnyelv”-nek nevezett for­ma­lizmust. E formalizmusban a sorozatok kezeléséhez az alábbi műveleteket eleve definiáltnak vesszük:

·              ( ) – üres sorozat,

·              si – i. eleme az s sorozatnak,

·              |s| – az s sorozat hossza, elemszáma,

·              (s1,..,si) – a sorozat első i db eleméből alkotott (rész)sorozat,

·              (si,..,s|s|) – a sorozat i. elemével kezdődő (rész)sorozat,

·              s&t – az s és a t sorozat elemeinek egymásután illesztése.

Minden összetett szerkezetet ennek a nyelvezetnek a „logikája” szerint jelölünk. Így járunk el a direktszorzat esetében is: Például: AxBxC halmaz egy elemét (a,b,c) jelöli, ahol termé­szetesen elvárható, hogy aÎA, bÎB, cÎC teljesül.

Az algebrai leírásban –mint láttuk– előbukkan olyan szituáció, amelyben a típusba tartozó adat nemdefiniált értékűvé vált, azaz hiba állapotba jutott. Ezt az állapotot hivatott rögzíteni az adatszerkezet egy logikai komponense. Tehát minden általunk leírt típusú adatnak mini­mum két komponense lesz: egy, az elemek sorozatát tartalmazó, és egy hibásságot rögzítő logikai komponens – TElem*xLogikai.

A verem export modulja [SzP1999, PSzZs1998] következik az alábbi példában.

Példa:

ExportModul Verem(Típus TElem):

   [Jelölések a specifikációhoz:
     1) Verem=SorozatxHiba, Sorozat=TElem*, Hiba=Logikai
     2) v' a Verem művelet utáni állapota, ha a bemenetitől meg kell
         különböztetni]

  Eljárás Üres(Változó v:Verem)
                                         [veremlétrehozás a memóriában]
     Ef:  Ø$vÎVerem [4]
     Uf:  $vÎVerem Ù v=((),Hamis)

  Függvény Üres?(Konstans v:Verem): Logikai
                                                       [üres-e a verem]
     Ef:  $vÎVerem Ù Øv.hiba
     Uf:  Üres?(v)=(v.sorozat=())

  Függvény Tele?(Konstans v:Verem): Logikai
          [a verem megtelt-e]
  Ef:
$vÎVerem Ù Øv.hiba
  Uf: Tele?(v)=(... a v nem bővíthető több elemmel ...)

  Függvény Tető(Változó v:Verem): TElem
                                          [a verem tetőelemének értéke]
     Ef:  $vÎVerem Ù Øv.hiba
     Uf:  v.sorozat=(v1,...,vn) Þ Tető(v)=vn
          v.sorozat=() Þ v'.hiba=Igaz

  Eljárás Verembe(Változó v:Verem, Konstans e:TElem)
                                                         [verembetétel]
     Ef:  $vÎVerem Ù Øv.hiba
     Uf:  v.sorozat=(v1,...,vn) Þ v'.sorozat=(v1,...,vn,e)
    Tele?(v)
Þ v'.hiba=Igaz

  Eljárás Veremből(Változó v:Verem, e:TElem)
                                                  [a tetőelem kivétele]
     Ef:  $vÎVerem Ù Øv.hiba
     Uf:  v.sorozat=(v1,...,vn) Þ v'.sorozat=(v1,...,vn-1) Ù e=vn
          v=() Þ v'.hiba=Igaz

  Függvény Hibás?(Változó v:Verem): Logikai
                                   [történt-e hiba a veremre hivatkozás
                                         során, törli a hibaváltozót]
     Ef:  $vÎVerem
     Uf:  Hibás?(v)=v.hiba Ù v'.hiba=Hamis

Modul vége.

1.2.3. A P/P-specifikáció algebraiba beilleszthetőségének megsejtése

Van tehát kétféle leírása a típusnak: egy összefüggő algebrai rendszer, axiómákból; valamint egy formálisan egyáltalán nem összefüggő P/P-specifikációkból álló mikrórendszerek hal­maza.[5] Az egymáshoz illeszkedés alatt értsük azt, hogy ha feltesszük a mikrórendszerek he­lyességét (azaz hogy, ha az előfeltétel teljesül, akkor az utófeltétel is teljesül), akkor azon axiómák is teljesülnek, amelyekben az adott mikrórendszerek szerepelnek.

Például, tegyük föl a Veremből és a Verembe operációk P/P-specifikációinak helyességét, akkor az 4o axióma is teljesül. A vizsgálandó állítás ekkor így hangzik:

Ha a

Verembe(v,e)
   Ef: 
$vÎVerem Ù Øv.hiba
   Uf:  v.sorozat=(v
1,...,vn) Þ v'.sorozat=(v1,...,vn,e) [6]
  Tele?(v)
Þ v'.hiba=Igaz

Veremből(v,e)
   Ef: 
$vÎVerem Ù Øv.hiba
   Uf:  v.sorozat=(v
1,...,vn) Þ v'.sorozat=(v1,...,vn-1) Ù e=vn
        v=()
Þ v'.hiba=Igaz

P/P-specifikációk helyesek, akkor teljesül a

„4o  Veremből – axióma” is, azaz a

   Ø Tele?(v)  Þ  Veremből(Verembe(v,e))=(v,e)  Ù

   Ø Üres?(v)  Þ  Verembe(Veremből(v))=v

Az egyszerűség kedvéért elégedjünk meg az axióma első felének ellenőrzésével, azaz a Veremből(Verembe(v,e))=(v,e)  formula teljesülésének belátásával!

Első lépésként a Verembe operációt kell figyelembe vennünk. Ennek előfeltételét ($vÎVerem Ù Øv.hiba), s az utófeltétel (v.sorozat=(v1,...,vn) Þ v'=(v1,...,vn,e)) elfogadjuk. A verem-állapot utófeltételben szereplő bemeneti és kimeneti állapotát egybe vetve (s az előfeltétel Øv.hiba részét tudomásul véve) az utófeltétel a következő alakban is írható:

(1)           v'.sorozat=v.sorozat & (e).

Másodikként a Veremből operációt elemezzük észrevéve, hogy ez esetben legalább 1-elemű sorozatra kell alkalmazni. Hasonló gondolatmenettel az utófeltétel itt is tömöríthető: v.sorozat =v'.sorozat&(vn) Ù e=vn, sőt még tovább:

(2)                          v.sorozat=v'.sorozat&(e).

Harmadik lépésben kövessük az axióma kiértékelését! Hogy elkerüljük az adatok keveredését, az egyes műveletek paramétereit indexeljük: 1 indexszel a Verembe művelet paramétereit és 2-vel a Veremből műveletét.

Bemenet

Verembe

Kimenet

(1)-ből

 

(3)

(v1.sorozat,
e1)

®

v1’.sorozat

=

v1.sorozat&(e1)

=

 

=

v2.sorozat

®

(v2'.sorozat,
e2)

 

 

 

Bemenet

Veremből

Kimenet

 

Szedjük össze az adatok közötti kapcsolatokat!

A Verembe-Veremből műveletek közötti paraméterátadás:

(3)

v1.sorozat&(e1)

=

v2.sorozat

A teljes transzformáció (Bemenet®Kimenet):

(BeKi)

(v1.sorozat,e1)

Veremből·Verembe

(v2’.sorozat,e2)

Az axióma:

(Ax)

(v,e)

Veremből·Verembe

(v,e)

A (2) v2-re aktualizált alakja:

(22)

v2.sorozat

=

v2'.sorozat&(e)

A (BeKi)–ből rögvest látszik, hogy a 2. komponensbeli e identikusan transzformálódik –ahogy (Ax) el is várná–, ha e1=e2 teljesül, amit semmi sem tilt. Tehát feltesszük. A fenti kap­csolatokban ezért ei helyettesíthető e-vel. Belátandó, hogy az 1. komponensbeli v is válto­zatlan marad.

A (3) miatt v2.sorozat helyett írhatjuk: v1.sorozat&(e1), de a (22)-vel összevetve v2'.sorozat-ra az adó­dik, hogy meg kell egyezzék v1.sorozat-tal, ami a (BeKi) bemenetén szerepelt a sorozat-kompo­nensként, s éppen ezt vártuk.

 

2. Prolog nyelvi minimum

Az alábbiakban a Borland által kifejlesztett Turbo Prolog nyelvjárásról esik néhány, össze­foglaló szó. [RKRB1988] Legfontosabb szintaktikus tudnivalókat az alábbi Prolog nyelvű programvázlat tartalmazza:

Domains
/*
A predikátumok (tények, szabályok) paramétertípusainak definiálása.
Általános alakja: ’típus = típuskonstrukció’. */

 unio = funktor1(típus1); funktor2(típus2); …
 iter = típus*
 dirSzor = funktor(típus1,típus2,…)
 

Predicates
/*
A tények és szabályok szignatúrájának (fejsor-szintaxis) deklarálása; minden, később előkerülő tény és szabály itt deklaráltatik:
*/

 ténySzabály(típusok felsorolása)
 

Database [7]
/*
A "dinamikusan" kezelendő tudás: szabályok.
Elhagyható szekció; de ha van, akkor nem lehet közös része a
'Predicates'-szel.
*/

 ténySzabály(típusok felsorolása)

Clauses
/*
Tények és szabályok:
*/

 tény(szignatúrának megfelelő paraméter-lista).
 
 szabály(szignatúrának megfelelő paraméter-lista)
  if logikai formula.
 

Goal
/*
A feladat megoldását „elindító” állítás:
*/

 logikai formula.

A tények: végülis névvel ellátott relációk. A megnevezett relációba hozzuk a paraméterként felsorolt (konstans) adatokat. A szabályok szintén névvel ellátott relációk, csakhogy a relációba bizonyos feltételekkel kerülnek a paraméterlistában felsorolt adatok. Ezek az úgynevezett: Horn-klózok.

Alakjuk: „Következmény(paraméterek) Ü felté­tel(paraméterek)”, ahol a „Ü” műveleti jel a szokástól eltérő irányú implikáció, a következmény: egy „nevesített” paraméteres szabály, a feltétel bizonyos korláto­zásokkal negációt is tartalmazhat, de jellemzőbben konjunkciót, ill. diszjunkciót. Értelem­szerűen a paraméterek most nem egyetlen „pontját” jelölik ki a sokdimenziós paraméter­térnek, hanem egy alterét. Más szóval: legalább egy paramétere nem konstansként lett rögzítve.

Jelentésük: a következmény teljesül (azaz a paraméterként felsorolt konstansok az adott nevű relációban állnak egymással), ha a feltétel igaz.

A későbbiek során Turbo Prolog program futásáról készült „pillanatfelvételekkel” is találkozunk, amelyek az alábbi ábrához nagyon hasonlatosak lesznek.

Az Editor „ablak” mutatja a forrásprogram egy darabkáját, míg a Dialog a program ered­ményét keretezi. (Ha látszanak is majd egyéb ablakok, számunkra lényegtelenek lesznek.)

3.1. ábra – Turbo Prolog program fejlesztői környezetének felépítése

 

3. Átírási szabályok

3.1. Algebrai és P/P-specifikáció mint egyetlen szabály-gyűjtemény

A Prolog-ban tehát minden tényként esetleg szabályként jelenik meg. Tehát át kell tudnunk írni az algebrai axiómáinkat éppúgy, mint a modulokban szereplő operátorok egyedi elő- és utófeltételre szétválasztott specifikációit. Ráadásul ezeket egyetlen rendszerré kell össze­kovácsolnunk, hogy vizsgálhassuk: mennyire működnek együtt.

3.2. Az operátor-szabály szignatúrája

Az operátor az algebrai leírásban egységesen függvényként jelenik meg. Értékkészlete gya­korta halmazok uniójaként, sőt néha halmazok direktszorzataként bukkan elő. Pl.: VeremÈ {NemDef}, vagy VeremxElem.

Mivel a vizsgálatunk szempontjából teljesen lényegtelen, hogy a típuskonstrukciók milyen elemtípuson nyugszanak, feltehetjük, hogy a bázistípus mindig (pl.) egész szám típus legyen.

A Prolog direktszorzat képzése ekként oldható meg: „hF(h1,h2,…)”., ahol hF – funktor, hX – komponens-típus.

Az unió megfelelője: „h1F(h1); h2F(h2);..”., ahol hXF – funktor, hX – alternatíva-típus.

Egy felsorolás típus az unió speciális, leegyszerűsített váltózatának is tekinthető, így átirata: „k1; k2;..”., ahol kX – alternatív konstansok.

Az iterált Prolog-beli párja: „h*”, ahol h – a bázistípus.

A fenti Prolog megfeleltetések ismeretében vermes példánk legbonyolultabb, „veremből” operáció szignatúrájában szereplő halmazok leírhatók. Mielőtt ezt megtennénk, meg kell gondolni, hogyan ültessük át a Prolog programba az operációkat. A probléma nyilvánvaló: a Prolog-ban minden tevékenység szabályként, tehát logikai értékkel rendelkező számítással fogalmazandó meg. Tehát a döntés egyértelmű:

Operátor(Értelmezési tartomány): Értékkészlet

Prolog párja:

Operátor(Értelmezési tartomány , Értékkészlet)

szignatúrájú szabály lesz.

Lehetnek a fenti általános operátor-sémától eltérő szignatúrájúak is: konstruktorok, illetve destruktorok. Ezek Prolog-megfelelői:

Konstruktor: Értékkészlet

Prolog párja:

Konstruktor(Értékkészlet)

szignatúrájú szabály lesz.

Destruktor(Értelmezési tartomány)

Prolog párja:

Destruktor(Értelmezési tartomány)

szignatúrájú szabály lesz.

Így már az operátorokat könnyedén átültethetjük a Prolog-ba, ha észrevesszük, hogy a NemDef érték mindig a verem „kóros” állapotára utal. Elegendő tehát a verem –ezidáig homályos– alap típusát bővíteni a {NemDef,Def} halmazzal. A verem tehát a veremelemek sorozatából[8] és a hibásságot jelző logikai érték kettőséből fog állni.

Mivel a verem állapotának szerves jellemzőjévé tettük a „kórosságot” jelző komponenst, ezért olyan operációk szabályában is fel fog bukkanni outputként a verem, amelyben eredetileg csak az inputon szerepelt (pl. Tető).[9]

Domains
  TNemDef = nemDef; def
  TElem = Integer
  TVerem = TElem*
  TVeremK = verem(TVerem,TNemDef)
  TLogikai = igaz; hamis

Predicates
  ures(TVeremK)
  uresE(TVeremK /*input*/,
        TLogikai  /*output*/)
  teto(TVeremK, /*input*/
       TElem,TVeremK  /*output*/)
  verembe(TVeremK,TElem,  /*input*/
          TVeremK  /*output*/)
  verembol(TVeremK,  /*input*/
           TElem,TVeremK  /*output*/)

3.3. Az algebrai leírás szabályokká alakítása

A „logika” kódolásához meg kell gondolnunk az alábbiakat:

1.      az algebrai specifikációban ki nem mondott (implicit) error-axióma megfogalmazása ope­rációról operációra (és az iniciális error-axióma)

2.      az algebrai axiómák mechanikus átírása

3.      az egyes operációk PP-specifikációjának Prolog-osítása

3.3.1. Implicit error-axiómák Prolog szabályai

Azt kell kifejeznünk, hogy ha a verem állapota (2. komponense) NemDef, akkor a transz­formáció tényleges számolás nélkül, „üresen” hajtódik végre:

Operátor(verem(_,nemDef), /* input veremparaméter */
         _, …           , /* további input paraméterek */
         verem(_,nemDef), /* output veremparaméter: változatlan */
         _, …          )  /* további output paraméterek */

Ezt tehát minden egyes operációra ki kell mondani!

Példák:

  uresE(verem(_,nemDef),_). /* ÜresE-implicit error-axióma */
  teto(verem(V,nemDef),_,verem(V,nemDef)). /* Tető-implicit error-
                                              axióma */

Magyarázat a két példához: az ÜresE operációt a NemDef állapotú veremre alkalmazva,  nincs szerepe az axióma igaz-értéke szempontjából sem a veremelemek sorozatának, sem az operáció által visszaadott éréknek. Ehhez hasonlóan a Tető operációt a NemDef állapotú veremre alkalmazva, az axióma „sikere” független kell legyen a második paraméter (a Tető által visszaadott érték) milyenségétől. Érdekes különbséget vehetünk észre a két példa para­méterezésében. Mindkettőben a verem­elemek sorozata számunkra érdektelen, de amíg az el­sőben a szabad paraméter „_” jelével élhettünk, addig a Tető példájában nem tehettük. Oka: ebben a szabályban hangsúlyoznunk kell, hogy a bemeneti és a kimeneti struktúrában a két paraméterértéknek meg kell egyezniük. Azaz bármi lehet a sorozat, de a kimenetkori azonos a bemenetkori értékkel.

3.3.2. Iniciális error-axióma szerepe

Mivel annak vizsgálata, hogy valamely operációt ideje korán (az adat létrehozását megelő­zőleg) hajtottunk-e végre, nem különösebben nehéz feladat, ezért eltekintünk ennek számba­vételétől.[10] Ha tehát valamely operáció előfeltételei között szerepel az „operálandó adat léte­zésére vonatkozó feltétel”, akkor azzal nem foglalkozunk.

3.3.3. Algebrai axióma Prolog szabálya

Filozófiánk lényegi kérdéséhez érkeztünk el. Hogyan is ellenőrizzük a kétféle specifikációból származó szabályok ellentmondás-mentességét? Egy lehetséges út vázlata a következő:

Mivel vizsgálatunk olyan típusra vonatkozik, amelyben valamilyen sorozat kezelését végzik a típusműveletek, és az axiómákban éppúgy, mint a P/P-szabályokban az üresség feltétele, vala­mint a definiáltság tűnik föl unos-untalan, ezért két szignifikáns esetet különböztetünk meg: az üres sorozat esetét, meg a nem üreset. S mindkettőn belül önálló aleset: legális vagy ille­gális állapotú-e a kiinduló sorozat. Az utóbbiban az az eset testesül meg, amikor vala­milyen hiba folytán az adatszerkezet NemDef állapotba jut. A kérdés ekkor: ebben a helyzetben az operá­ciók mit tesznek az adatta. Mivel ekkor megállapodás szerint bármely operáció transz­formáció nélkül „mű­ködik”, amit az implicit error-axióma szerint kell kódolnunk, így azon axiómák, amelyekben szerepelnek, nagy eséllyel nem fognak teljesülni. Ez a negatív ered­mény azonban semmi információt nem hordoz. (Sőt a ritka teljesülése sem.) Ezért nem fogjuk a NemDef ál­lapotot külön tesztesetben vizsgálni.

Az algebrai specifikáció axiómáit tekintsük olyan szabályoknak, amelyeknek két paramétere van: az egyik a kiinduló struktúra (amely lehet üres, vagy nem üres; lehet NemDef vagy Def állapotban); a másik explicite fejezi ki az algebrai axióma teljesülését (Igaz) vagy nem tel­jesülését (Hamis). Szignatúrája:

Axióma(TVeremK /*mintastruktúra*/,TLogikai /*teljesül-e*/)

A szabály meggondolást igénylő része, a klóz feltételrésze. Ez lesz az algebrai axiómában szereplő logikai kifejezés Prolog-os párja. Vizsgálni kell a „megengedett állapotban” levő, legális adatokra (Def) és az illegálisokra (NemDef) vonatkozó alkalmazás esetét. Az illegális értékelése egyszerű, hiszen ilyen adatokra alkalmazáskor nem fog az axióma sérülni; ugyanis a definiáltság vagy nemdefináltság kérdését az axiómákon belül rögzítjük.

Példa:

Algebrai specifikáció egy axiómája

Az axióma szabályalakja

1o  Üres – axióma

          Üres?(Üres)

Predicates

 
  axioma1(TVeremK,TLogikai)

 

Clauses

 
  /* def-re: */

  axioma1(V,igaz) if

    ures(V) and uresE(V,igaz).

  /* nemDef-re:Igaz */
  axioma1(verem(_,nemDef),igaz).
  /* más esetekben: */

  axioma1(_,hamis).

Olyan algebrai axiómák esetén, amelyben jól elválasztható részekre bontható az állítás, mind­egyikhez önálló szabályt kell fogalmazni. (Ez később általánosan követendő elvvé fog válni!)

Példa:

Algebrai specifikáció egy axiómája

Az axióma szabályalakja

4o  Veremből – axióma

        Veremből(Verembe(v,e))=(v,e)  Ù
Ø Üres?(v)  Þ  Verembe(Veremből(v))=v

Predicates

 
  axioma4(TVeremK,TLogikai)
  axioma4_1(TVeremK,TLogikai)
  axioma4_2(TVeremK,TLogikai)
 

Clauses

 

  axioma4(V,igaz) if
    axioma4_1(V,igaz) and
    axioma4_2(V,igaz).

  axioma4(_,hamis).

  axioma4_1(V,igaz) if

    verembe(V,E,W) and
    veremből(W,E,V).

  axioma4_2(V,igaz) if

    uresE(V,hamis) and
    veremből(V,E,W) and
    verembe(W,E,V)
  or
    uresE(V,igaz).
 

 

Megjegyzés: az axióma4_2 kódolásánál egy implikáció Prolog megfelelőjét kellett megta­lálnunk. Erről később lesz még szó. Illetve a „ØÜres?(v) tagadás helyett az ellenkezőjét állítottuk.

3.3.4. P/P-specifikáció Prolog szabálya

Itt válik elkerülhetetlenné a döntés a TVerem típus mibenlétéről: legyen TElem bázistípusú sorozat. A sorozatok kezelése a konkrét feladatunktól független, előre kidolgozható. Az aláb­bi eszközkészlet tartozik, előre definiáltként, a programhoz include-ként hozzá­illeszt­hetőként[11]:

·              Elem(sorozat,i) – i. eleme a sorozatnak,

·              Hossz(sorozat) – sorozat-hossz,

·              Eleje(sorozat,i) – a sorozat első i db eleméből alkotott (rész)sorozat,

·              Vége(sorozat,i) – a sorozat i. elemével kezdődő (rész)sorozat,

·              EgymásUtán(sorozat1,sorozat2) – a két sorozat elemeinek egymásután illesz­tése.

A példánk esetében nincs is szükség a fenti speciális kezelő műveletekre, elegendőek a Prolog eleve meglévő sorozat-kezelő eszközei.

Visszatérve a P/P-átírásra, logikusnak látszik olyan szabály hozzárendelése, amelyben az utó­feltételben leírt állapotot a szabály következmény paramétereivel rögzítjük –ha lehet–, s az előfeltételt pedig a szabály „if” utáni feltétel részében fogalmazzuk meg.

Példa:

Operáció P/P-specifikációja

Operáció szabálya

Függvény Üres?(Konstans v:Verem): Logikai
  Ef: $vÎVerem Ù Øv.hiba
  Uf: Üres?(v)=(v.sorozat=())

uresE(verem([],D),igaz) if
  D
¹nemDef.
uresE(_,hamis).

Megjegyzés: az előfeltétel egy részletének áthúzásával jeleztük, hogy az átírásban nem játszik szerepet.

A 2. paraméter típusa: Logikai. Ezért kézenfekvő 2 szabály-alternatívában gondolkodni. Egyik az „igaz”, másik a „hamis” értékhez tartozik.

A szabály első alternatíváját egyszerűb­ben is írhatjuk.Ha kihasználjuk, hogy D¹nemDef ugyanaz, mint D=def. Íme:

 

uresE(verem([],def),igaz).

Olyankor, amikor az utófeltétel több kijelentést egyesít, az egyes kijelentéseket önálló sza­bály-alternatívaként kell megfogalmazni. Ez akkor könnyen felismerhető, ha az utófeltétel implikációk konjunkciója. Sokszor az implikáció feltételrésze a szabály paraméterlistájában (illesztési feltétel gyanánt) helyezhető el. Máskor a szabály feltételrészében kell a komplett implikációt kódolni.

Példa:

Operáció P/P-specifikációja

Operáció szabálya

Függvény Tető(Változó v:Verem): TElem
  Ef: $vÎVerem Ù Øv.hiba
  Uf: v.sorozat=(v1,...,vn) Þ Tető(v)=vn
      v.sorozat=() Þ v'.hiba=Igaz

teto(verem([Vn|V],def),Vn,
     verem([Vn|V],def)).

teto(verem([],def),_,
     verem([],nemDef))

Megjegyzés: valóban alternatívával kódolható, hiszen a feltételek egymást kizárják, azaz közülük csak egyetlen egy lehet igaz.

3.4. Az implikáció kódolása

A végső egyesítésnek már talán csak egyetlen akadálya maradt[12], amely több helyen is föl­vetődhet: hogyan ültethető át egy implikációt tartalmazó formula? A Prolog leírási és vég­rehajtási szabályait figyelembe véve a redundánsnak tűnő „A Þ B®A Ù B Ú ØA” (ekvivalens) helyettesítéssel élünk.

Például (4o algebrai axióma részlete –ahogy már láttuk–):

implikáció

szabály

Ø Üres?(v)  Þ  Verembe(Veremből(v))=v

  uresE(V,hamis) and
  veremből(V,E,W) and
  verembe(W,E,V)
  or
  uresE(V,igaz).

Hasonló problémát kell megoldani a két hiba-axiómánál. Nagyon fontos, hogy a későbbi –esetleges– transzformációk során is megmaradjon az implikáció integritása! (L. még itt!)

3.5. A szabályok programmá egyesítése

Egyetlen kérdés maradt hátra: hogyan késztethető a Prolog rendszer arra, hogy az eddig egybe fésült szabályrendszer illeszkedését kimutassa. A válasz roppant egyszerű: célállításként fo­galmazzuk meg (2-2, korábban megfogalmazott paraméterrel) az axiómák konjukcióját, s ír­juk ki a kiszámítás után kapott paraméter értéket. Ideális esetben minden axiómára mind üres, illetve nem üres (s ahogy már megindokoltuk: csak „legális” állapotú) adatra „igaz” lesz a válasz. Valahogy így:

Példa:

Goal

  /*(üres,def)-re igaz-e:*/
  V0X=struktura([],def) and
  axiomaX(V0,JoE0X) and
  write("X:",JoE0X) and write(" ",V0X,"-re") and nl

and

  /*(nem üres,def)-re igaz-e:*/
  VX =struktura([_],def) and
  axiomaX(VX,JoEX) and
  write("X:",JoEX) and write(" ",VX,"-re.") and nl

Egy apró megjegyzés a kódolás könnyebb megértéséhez: furcsa azonosítójú változókkal talál­kozhatnunk a célállításban. A kulcs ennyi: „V” veremről van szó, „0” üres, vagy ha a nulla elmarad, akkor nem üres,  „X” az X-edik axióma tesztelésében vesz részt. Például: „V01” = „olyan verem, amely 0 darab eleme van, és az 1. axióma tesztelésében vesz részt”, „V2” = „azt a veremet jelöli, amely nem üres (nincs 0 az azonosítóban), és a 2. axióma teszteléséhez használjuk”… A Prolog logikájából fakadóan nincs mód ugyanazon változó kétféle értékkel tőrténő „újra­felhasználásra”. Emiatt nem hagyható el az „X” az azonosító nevéből.

A fentiek alapján a kódolás hiánytalanul elvégezhető. Vegyük észre, hogy a Hibás? operátor elő sem bukkan az axiómákban, ugyanis a specifikáció algoritmikus lépésében született. Hiá­ba van rá vonatkozó (operátor-) szabály, az a Prolog program számára láthatatlan, tesz­telhe­tetlen marad, hiszen a célállítás axióma-centrikus: nincs axióma, amely „mozgásba igye­kezné hozni”. Két dolgot tehetünk: vagy utólag hozzávesszük ezt az operátort az algebrai leíráshoz, és újabb axiómával fejezzük ki a vele szembeni elvárásainkat[13], vagy nem vesszük figyelembe a kompatibilitás ellenőrzésekor. Most az utóbbit választjuk, mondván: nem vala­mifajta ekvi­valenciát tartozunk bizonyítani, hanem a P/P-specifikáció algebraihoz illeszke­dését; másrészt egyszerű működése könnyen, szemmel is ellenőrizhető.

Egyetlen fontos megjegyzést kell még tennünk. Mivel az a cél, hogy a specifikáció esetleges hibáira a Prolog program ellenőrzött vágányokon maradjon, s úgy mutassa ki ezeket, számol­nunk kell a Prolog alapvető hozzáállására: backtrack-kel keresi az illeszkedő mintát. Tehát ahol garantáltan értelmetlen a backtrack, ott meg kell akadályozni a visszalépést. Így kell ten­ni –nyilvánvalóan– a célállítás egyes logikailag független részei között, de az axiómák eseté­ben is számos helyen.

A megakadályozás Prolog eszköze a vágás. A vágás operátor működése két ponton is befo­lyásolja a végrehajtást:

1.      a szabályban a vágás előtti formulákra már nem tér vissza, és a szabály Fail-lel zárul;

2.      továbbá a szabály eddig még nem vizsgált alternatíváit sem fogja vizsgálni.

Emiatt a vágást tartalmazó algebrai axiómákhoz tartozó szabályok utolsó, egyébként éppen az algebrai axióma sérülését kijelentő alternatívára nem kerülhet a sor. Ez komoly gond. Kike­rülésére persze van mód: egyesítsük egy önálló „al-szabályban” a vágást tartalmazó formulát, s így annak (egyébként nem levő) alternatívái maradnak ki a visszalépéskori végrehajtásból, de az egy szinttel feljebbi „axiomatikus” szabály további alternatívái zavartalanul érvénye­sül­hetnek. Ügyelni kell persze arra, hogy az implikációk kódolásánál két ágra szakadt kód-darabok egyetlen al-szabályban maradjanak.

Példa:

szabály vágással

szabály korrekt vágással

axioma3(V,igaz) if
  verembe(V,E,W) and ! and
 
 teto(W,E,W).

axioma3(V,igaz) if
  axioma3_1(V,igaz).

axioma3_1(V,igaz) if
  verembe(V,E,W) and ! and
  teto(W,E,W).

S egy implikációt tartalmazó példa:

szabály vágással

szabály korrekt vágással

axioma3H(verem(_,def),igaz) if
  uresE(verem(_,def),igaz) and ! and
 
teto(verem(_,def),_,verem(_,nemDef)
or
  uresE(_,hamis).

axioma3H(verem(_,def),igaz) if
  axioma3H_1.

Axioma3H_1(verem(_,def),igaz) if
  uresE(verem(_,def),igaz) and ! and
 
teto(verem(_,def),_,verem(_,nemDef)
or
  uresE(_,hamis).

Végezetül következzék a veremnek a Prolog rendszerben egyesített specifikációja!

Példa:

Domains

  TNemDef = nemDef; def
  TElem = Integer
  TVerem = TElem*
  TVeremK = verem(TVerem,TNemDef)
  TLogikai = igaz; hamis

Predicates

  ures(TVeremK)
  uresE(TVeremK /*input*/,
        TLogikai  /*output*/)
  teto(TVeremK, /*input*/
       TElem,TVeremK  /*output*/)
  verembe(TVeremK,TElem,  /*input*/
          TVeremK  /*output*/)
  verembol(TVeremK,  /*input*/
           TElem,TVeremK  /*output*/)

  axioma1(TVeremK,TLogikai /* teljesül-e */)
  axioma1_1(TVeremK,TLogikai /* teljesül-e */)
  axioma2(TVeremK,TLogikai /* teljesül-e */)
  axioma2_1(TVeremK,TLogikai /* teljesül-e */)
  axioma3(TVeremK,TLogikai /* teljesül-e */)
  axioma3_1(TVeremK,TLogikai /* teljesül-e */)
  axioma3H(TVeremK,TLogikai /* teljesül-e */) /* hiba-axióma */
  axioma3H_1(TVeremK,TLogikai /* teljesül-e */) /* hiba-axióma */
  axioma4(TVeremK,TLogikai /* teljesül-e */)
  axioma4_1(TVeremK,TLogikai /* teljesül-e */)
  axioma4_2(TVeremK,TLogikai /* teljesül-e */)
  axioma4(TVeremK,TLogikai /* teljesül-e */)
  axioma4H(TVeremK,TLogikai /* teljesül-e */) /* hiba-axióma */
  axioma4H_1(TVeremK,TLogikai /* teljesül-e */) /* hiba-axióma */

Clauses

  ures(verem([],def)).

  uresE(verem(_,nemDef),_). /* ImpErrAx */
  uresE(verem([],def),igaz).
  uresE(verem([_],def),hamis).
  uresE(verem([_|_],def),hamis).

  teto(verem(V,nemDef),_,verem(V,nemDef)). /* ImpErrAx */
  teto(verem([E|V],def),E,verem([E|V],def)). /* PP */
  teto(verem([],def),_,verem([],nemDef)). /* PP: Err */

  verembe(verem(V,nemDef),_,verem(V,nemDef)). /* ImpErrAx */
  verembe(verem(V,def),E,verem([E|V],def)). /* PP */

  verembol(verem(V,nemDef),_,verem(V,nemDef)). /* ImpErrAx */
  verembol(verem([E|V],def),E,verem(V,def)).  /* PP */
  verembol(verem([],def),_,verem(_,nemDef)).  /* PP: Err */

  axioma1(V,igaz) if
    axioma1_1(V,igaz).
  axioma1(_,hamis).

   axioma1_1(V,igaz) if
     ures(V) and uresE(V,igaz).

  axioma2(V,igaz) if
    axioma2_1(V,igaz).
  axioma2(_,hamis).

   axioma2_1(V,igaz) if
     verembe(V,_,verem(W,def)) and ! and
     uresE(verem(W,def),hamis).

  axioma3(V,igaz)if
   
axioma3_1(V,igaz)
  axioma3(_,hamis).

   axioma3_1(V,igaz) if
     verembe(V,E,W) and ! and teto(W,E,W).

  axioma3H(verem(_,def),igaz) if
 
  axioma3H_1(verem(_,def),igaz).
  axioma3H(_,hamis).

   axioma3H_1(verem(_,def),igaz) if
     uresE(verem(_,def),igaz) and ! and
    
teto(verem(_,def),_,verem(_,nemDef)
    or
      uresE(_,hamis).
  axioma4(V,igaz) if
    axioma4_1(V,igaz) and axioma4_2(V,igaz).
  axioma4(_,hamis).

   axioma4_1(V,igaz) if
     verembe(V,E,W) and ! and verembol(W,E,V).

   axioma4_2(V,igaz) if
     uresE(V,hamis) and ! and verembol(V,E,W) and ! and verembe(W,E,V)
   or
     uresE(V,igaz).

  axioma4H(verem(_,def),igaz) if
 
  axioma4H_1(verem(_,def),igaz).
  axioma4H(_,hamis).

   axioma4H_1(verem(_,def),igaz) if
     uresE(verem(_,def),igaz) and ! and
    
verembol(verem(_,def),_,verem(_,nemDef)
    or
      uresE(_,hamis).
Goal

  clearwindow and

/* 1. axióma tesztje: */

/*(üres,def)-re igaz-e:*/
  V01=verem([],def) and
  axioma1(V01,JoE01) and
  write("1:",JoE01) and write(" ",V01,"-re") and nl
and ! and
  /*(nem üres,def)-re igaz-e:*/
  V1=verem([_],def) and
  axioma1(V1,JoE1) and
  write("1:",JoE1) and write(" ",V1,"-re.") and nl

and ! and
  … /* … és a többi axiómára is,
       értelemszerű azonosító-módosításokkal … */

Megjegyzés: a további axiómák beillesztése semmi elvi gondot nem jelent, csak bántóan unal­mas. Ezt a „gyakorlati” problémát ki lehet küszöbölni a kód a lényeget nem érintő átalakítá­sával (néhány ismétlődő funkció önálló szabályba szervezésével, elkülönítésével). A B. függelék tartalmaz egy ilyen céllal módosított Prolog programot.

4. A puding próbája

Lefuttatva az így megtervezett és kódolt Prolog programot némi csalódást hozott az ered­mény. L. a 4.1. ábrát!

4.1. ábra – futási eredmény
a verem hiányosan specifikált axióma esetén

Mint látható, az első axióma második tesztesete (tudniillik: nem üres, definiált, azaz legális ál­lapotban levő veremre alkalmazva) hamis eredményre vezetett! Elemezve az axióma megle­hetősen rövid szövegét, és belegondolva mondanivalójába, rájöhetünk: az axióma nem „tel­jes”. Állítása, hogy „Üres?(Üres)”, nem fejezi ki azt, mi legyen az Üres? függvény válasza a nem 0 elemet tartalmazó verem esetén.

Fogtunk tehát egy hibát? Nem! Az axiómarendszer nem hibás, hiszen a „nem üres verem” esetével a 2. axióma foglalkozik. Az persze kétségtelen, hogy az érintett axióma önmagában nem „teljes”. Ha el akarjuk kerülni a teszt szigorú „kritikáját”, módosítanunk kell az 1. axió­mát: „v=Üres Û Üres?(v)”. A kódmódosítás a Clauses részt érinti. A meglévő „le­gális” adatra vonatkozó igaz-értékű kijelentés az üres veremre alkalmazható. Mondjuk ki tehát, hogy a nem üres veremre az Üres? függvény hamissal válaszol:

Clauses

 
  axioma1(V,igaz) if
    axioma1_1(V,igaz).
  axioma1(V,igaz) if
    axioma1_2(V,igaz).
  axioma1(verem(_,nemDef),igaz).
  axioma1(_,hamis).
   axioma1_1(V,igaz) if
     ures(V) and uresE(V,igaz).
   axioma1_2(V,igaz) if
     not(ures(V)) and not(uresE(V,igaz)).
 

4.2. ábra – futási eredmény
javított verem specifikáció után

Ez rávilágít arra, hogy számítani kell olyan axiómarendszerre is, amelynél több axióma fogal­maz meg egy, a tesztelési filozófiánk szempontjából egybe tartozó állítást. Az ilyenkor kijövő „kritika” nem jelent feltétlen hibát. Külön vizsgálatot igényel.

Tipikusabb hiba az, amikor egy operátor előfeltétel hiányos. Idézzük elő ezt úgy, hogy pl. a Tető operátor előfeltételét egyszerűen kihagyjuk. [Pr2]

4.3. ábra – futási eredmény
 hiányos Tető specifikáció esetén

A módszer megnyugtatóan szigorúnak (talán túlzottan is szigorúnak?!?) mutatkozott.

Hasznos további kísérletnek látszik a veremmel kapcsolatban kimondani állításokat, s ellenő­rizni őket a program definiálta verem-szabályok terében. Lássunk erre is egy példát! [Pr3]

A verem egy jól ismert tulajdonságát fogalmazhatjuk meg a következő tétellel: „A verem egy LIFO szerkezet”. Ezt így formalizálhatjuk:

"nÎN : ( v0=Üres Ù "iÎ[1..n] : vi=Verembe(vi-1,i) Ù

                wn=vn Ù "kÎ[1..n] : ( Veremből(wn-k+1)=(wn-k,en-k+1) Ù en-k+1=n-k+1 ) )

A „bizonyítás” a következő:

1.           Töltsük föl a vermet 1..N számokkal, i. elemének az értéke legyen i, tehát egyezzen meg a betétel sorszámával, az időrend könnyű ellenőrzése kedvéért.

2.           Vegyük ki belőle az elemeket, és ellenőrizzük, hogy a k.-ként kivett az N–k+1.-ként betett elem-e (vagyis az N–k+1 értékű), épp ez jelenti a bizonyítandó fordított sorrendet!

Az előbbiekben felépített program kisebb módosításával kaphatjuk a megoldó programot. A Prolog axiómák és szabályok közül kihagyhatók azok, amelyek az algebrai specifikációhoz tartoznak, a Goal rész teljesen kicserélődik.

Predicates

 
  ellenoriz(TVeremK,Integer)
  letrehoz(TVeremK,Integer)
  allitas(Integer)

Clauses

 
  allitas(N) if
    letrehoz(V,N) /*(1,2,..,N) verem létrehozása*/ and
    ellenoriz(V,N) /*(N,...,2,1) sorrendben vehetők ki az elemek*/.

  letrehoz(V,0) if
    ures(V).
  letrehoz(V,N) if
    N-1=N1 and letrehoz(V1,N1) and verembe(V1,N,V).

  ellenoriz(_,0).
  ellenoriz(V,N) if
    N-1=N1 and verembol(V,E,V1) and E=N and
    ellenoriz(V1,N1).

Goal

  clearwindow and N=… ide irandó a veremhossz … and
  allitas(N) and
  Write("Állitás igaz ",N,"-elemű veremre.") and nl

Mivel esetleges hibák kimutatása a cél, jobb felkészülni a program komolyabb eltévelyedé­seire:

1.      valamilyen illeszkedési gond miatti „végtelen” keresési ciklusba esés,

2.      nemdefiniált érték váratlan kijövetele.

Ezekre a hibalehetőségekre gondolva „bonyolítjuk” a programunk szabályait eképpen. (A kód módosításait kiemeltük.)


  allitas(N) if
    letrehoz(V,N) /*(1,2,..,N) verem létrehozása*/ and
    ! and /*az előzőn túljutva visszalépésnek nincs értelme*/
    write(V) and nl and
    V=verem(_,def) /*nem volt illegális művelet*/ and
    ! and /*az eddigieken túljutva visszalépésnek nincs értelme*/
    ellenoriz(V,N) /*(N,...,2,1) sorrendben vehetők ki az elemek*/.


  letrehoz(V,N) if
    N-1=N1 and N1>=0 and letrehoz(V1,N1) and verembe(V1,N,V) and
    V=verem(_,def) /*nem volt illegális művelet*/
   or
    write("Hiba a generáláskor.") and nl and fail
.


  ellenoriz(V,N) if
    N-1=N1 and N1>=0 and verembol(V,E,V1) and E=N and
    ellenoriz(V1,N1) and
    V=verem(_,def) /*nem volt illegális művelet*/
   or
    write("Hiba az ellenőrzéskor.") and nl and fail
.

Az állítás szabály nyomkövetését segíti csak a középtájon elhelyezett write ope­rátor; lényegibb közlendője van a létrehoz és az ellenőriz szabályokban elhe­lyezett kiírá­soknak: „Hiba … !”. A hiba-ágak végén található Fail a szabály nem teljesülését erőszakolja ki, így gátoljuk meg a futás értelmetlen folytatását. Ezt figyel­hetjük meg az alábbi szándé­kosan elrontott spe­cifikáció esetén. A hiba: a Verembe operátor duplán helyezi be az elemet, ezzel elrontja az elemek várt egymásrakövetkezését:

verembe(verem(V,def),E,verem([E,E|V],def)).

A program jelzését mutatja a 4.4. ábra.

4.4. ábra – futási eredmény
állítás-ellenőrzés rossz Verembe specifikáció esetén

Megjegyzés: a hibajelzés duplázódásának az az oka, hogy az elsőként kivett számot (5) jónak találta („… verembol(V,E,V1) and E=N …”, E is, N is 5), s továbbhaladva a következőnél (a 4 helyett 5-öt találva; N=4, E=5) akadt el. Így ezt is, meg visszalépve az előzőhöz, azt is el kellett vetnie („! and write … and fail”). Vegyük észre, hogy valójában felesleges a „hi­ba-ágban” a vágás, hiszen sem a létrehoz, sem az ellenőriz szabályoknak nincs más alternatívája, amihez mintaillesztés ürügyén visszatérhetne a Prolog program. [Pr4]

5. Összefoglalás

A módszerre visszatekintünk, és röviden összefoglaljuk, hogy a kétféle specifikáció kellékeit hogyan egyesítjük egyetlen Prolog programban.

4.1. Az operációk szabályalakja (Domains/Predicates)

Operátor(Értelmezési tartomány): Értékkészlet

Prolog párja:

Operátor(Értelmezési tartomány , Értékkészlet)

szignatúrájú szabály lesz.

Mind az értelmezési tartományt, mind az értékkészletet, ha halmazok direktszorzatából te­vődik össze, akkor paraméterek listájával írjuk le. A „fő” paraméter (azaz a vizsgált struk­túra) helyére, a definiáltságot is tartalmazó kibővített típust kell, írjuk. (Konstruktor és destruktor esetén az egyik paraméter –értelemszerűen– elmarad.)

A Prolog program ezek után fixálódott deklarációs része az alábbi lesz, amelyben típustól (osztálytól) függően mindössze a TElem típus marad kitöltendő, és természetesen az ope­rá­torok szignatúrái. (A változó részeket kiemeltük.) Látható, hogy bizonyos nyelvi-kódolási konvenciókat követünk:

·              Típusok neve „T”-vel kezdődik.

·              A Prolog kódolás kedvéért „bővített” struktúra neve az „absztrakt” neve + „K”.

Domains

  TElem = ??                                 /* generic-paraméter */
  TNemDef = nemDef; def
  TStruktura = TElem*
  TStrukturaK = sruktura(TStruktura,TNemDef)
  TLogikai = igaz; hamis

Predicates

  Op1(TStrukturaK … /* input paraméterek */,
      TStrukturaK … /* output paraméterek */)
[14]

 

4.2. Az operátorok P/P-specifikációjának szabálymegfelelője (Clauses)

A Prolog Clauses részbe kerülnek az operátorok szabályai. Elsőként a rávonatkozó implicit error-axióma megfelelőjét kell megfogalmazni, majd PP-specifikációt öntjük Prolog szabály alakjába; ha a használatkor hiba-állapotba (nemDef) jut a struktúra, akkor ehhez is tartozik szabály.

Ha az operátor működése során a struktúra állapotát nem módosítja (az ilyeneknek az export modulban is függvényt feleltetünk meg), akkor

Clauses

 
  /* Implicit error-exióma: */
  Op1(struktura(_,nemDef), /* input struktúraparaméter: „szabad” */
      _, …           , /*
további input paraméterek */
      _, …          ).  /*
output paraméterek „szabadok” */
  /* PP-specifikáció: */
  Op1(struktura(ST,def), /* input struktúraparaméter */
      _, …           , /*
további input paraméterek */
      _, …          ).  /*
output paraméterek */
 

Ha az operátor paraméterlistáján mind input, mind output szerepben felbukkan a struktúra pa­raméter (ezek szerepelnek az export modulban eljárásként), akkor

 
  /* Implicit error-exióma: */
  Opi(struktura(ST,nemDef), /* input struktúraparaméter */
      _, …           , /*
további input paraméterek „szabadok” */
      struktura(ST,nemDef), /*
output struktúraparaméter: változatlan */
      _, …          ).  /*
további output paraméterek „szabadok” */

  /* PP-specifikáció: */
  Opi(struktura(STi,nemDef), /* input struktúraparaméter */
      _, …           , /*
további input paraméterek */
      struktura(STo,nemDef), /*
output struktúraparaméter */
      _, …          ).  /*
további output paraméterek */

  /* esetleg, PP-specifikáció: hiba-állapotba jutás */
  Op1(struktura(ST,def), /* input struktúraparaméter */
      _, …           , /*
további input paraméterek */
      struktura(ST,nemDef), /*
output struktúraparaméter: változatlan;
                                  
állapotváltozás: def®nemDef */
      _, …          ).  /*
további output paraméterek „szabadok” */

Vegyük észre, minden operátorhoz legalább két Prolog szabály tartozik.

4.3. Az algebrai axiómák mint szabályok (Predicates/Clauses)

Az axiómák szignatúráját először a Prolog Predicates részében kell definiálni:

Predicates

  Ax1(TStrukturaK /*a fő struktúra*/,TLogikai /*teljesül-e*/)
 

Majd az összefüggést a Clauses részben helyezzük el, átültetve a formálisan megadott axi­ómát:

Clauses

 
 
Ax1(TSk /*fő típus*/, IVN /*igen vagy nem*/) if
    … a formális axióma kódja, a szereplő függvények Prolog függvényes
      párjának hívásával …
 
Ax1(_,hamis). /* minden egyéb esetre hamis */
 

Vegyük észre, minden axiómához legalább két Prolog szabály tartozik.

4.4. Az összeillés vizsgálatának kezdeményezése (Goal)

A tényleges ellenőrzést „kiváltó” célállítást minden axiómára külön-külön két esetre kell megfogalmazni:

·              az üres (és definiált) struktúrára,

·              a nem üres (és definiált) struktúrára:

Goal

  clearwindow and

  /*(üres,def)-re igaz-e:*/
  ST0=struktura([],def) and
  ax1(ST0,JoE0) and
  write("1:",JoE0) and write(" ",ST0,"-re") and nl

and ! /*a visszalépés értelmetlen*/ and
  /*(nem üres,def)-re igaz-e:*/
  ST=struktura([_],def) and
  ax1(ST,JoE) and
  write("1:",JoE) and write(" ",ST,"-re.") and nl
and ! /*a visszalépés értelmetlen*/ and

  … /* … és a többi axiómára is … */

Irodalom

[KV2001]

Kozma L. – Varga L.: „Adattípusok osztálya.”, ELTE TTK Informatikai TanszékCsoport, 2001

[FH2002]

Fekete I. – Hunyadvári L.: „Az adattípus absztrakciós szintjei”, Informatika a felső­okta­tásban’99 konferen­cia, Debrecen, 2002

[SzPG2002]

Szlávi P. – Pap Gné: „Adatszerkezet – Típusok”, Informatika a felsőoktatásban’99 konferen­cia, Debrecen, 2002

[PSzZs1998]

Pap Gné – Szlávi P. – Zsakó L.: „Módszeres programozás – Adattípusok”, ELTE TTK In­formatikai TanszékCsoport, 1998

[GN1986]

N. Gehani – A.D.McGettrich: „Software Specification Techniques.”, Addison-Wesley, 1986

[SzZs2000]

Szlávi P. – Zsakó L.: „Módszeres programozás – Gráfok, gráfalgoritmusok.”, ELTE TTK Informatikai TanszékCsoport, 2000

[SzP1999]

Szlávi P.: „Programok, programspecifikációk.”, Informatika a felsőoktatásban’99 konferencia, Debrecen, 1999

[RKRB1988]

Rich,K.–Robinson,Ph.: „Using Turbo Prolog.”, Borland-Osborne/McGraw-Hill, 1988

Programhivatkozások

[Pr1]

Szlávi P.: „A verem specifikációinak kompatibilitását ellenőrző Prolog program.”, http://izzo.inf.elte.hu/szlavi/Doktori/TipSpecKomp/VEREM4.PRO

[Pr2]

Szlávi P.: „A verem specifikációinak kompatibilitását ellenőrző Prolog program – hibás P/P-specifikációval.”, http://izzo.inf.elte.hu/szlavi/Doktori/TipSpecKomp/VEREM4H2.PRO

[Pr3]

Szlávi P.: „A verem specifikációinak kompatibilitását ellenőrző Prolog program – egy állítás ellenőrzése.”,. http://izzo.inf.elte.hu/szlavi/Doktori/TipSpecKomp/VEREMALL.PRO

[Pr4]

Szlávi P.: „A verem specifikációinak kompatibilitását ellenőrző Prolog program – egy állítás ellenőrzése, hibás P/P-specifikáció mellett.”, http://izzo.inf.elte.hu/szlavi/Doktori/TipSpecKomp/VEREMALH.PRO

 

Függelék

A. Sorozat betét

Az alábbi kóddal a lényeget igyekszünk kifejezni. A külön „Sorozat.inc” file-ba kiemelt kódot az  include „Sorozat.inc”  Prolog utasítással illeszthetjük a mindenkori prog­ra­munkhoz.

Domains
  TSorozat = TElem*

Predicates
  % a sorozatnak elem-e az adottadik:
  eleme(TSorozat, Integer /*dik*/, TElem, TLogikai)
  % a sorozatnak hossza:
  hossz(TSorozat, Integer)
  % a két sorozat elemei azonosak-e az adott sorszámig:
  azonos(TSorozat, TSorozat, Integer /*ig*/)

 

Clauses
  eleme([],_,_,hamis).
  eleme([E|_],1,E,igaz).
  eleme([_|S],I,EE,igaz) if
    II=I-1 and I>1 and eleme(S,II,EE,igaz).

  hossz([],0).
  hossz([_|S],H) if
    hossz(S,HH) and H=HH+1.

  azonos(_,_,0).
  azonos([E|S],[E|T],H) if
    HH=H-1 and azonos(S,T).

 

B. Módosított Prolog program

Kiemeléssel jeleztük az eltérést. [Pr1]

Domains

  TNemDef = nemDef; def
  TElem = Integer
  TVerem = TElem*
  TVeremK = verem(TVerem,TNemDef)
  TLogikai = igaz; hamis

Predicates

  ures(TVeremK)
  uresE(TVeremK /*input*/,
        TLogikai  /*output*/)
  teto(TVeremK, /*input*/
       TElem,TVeremK  /*output*/)
  verembe(TVeremK,TElem,  /*input*/
          TVeremK  /*output*/)
  verembol(TVeremK,  /*input*/
           TElem,TVeremK  /*output*/)

  axioma1(TVeremK,TLogikai /* teljesül-e */)
  axioma1_1(TVeremK,TLogikai /* teljesül-e */)
  axioma1_2(TVeremK,TLogikai /* teljesül-e */)
  axioma2(TVeremK,TLogikai /* teljesül-e */)
  axioma2_1(TVeremK,TLogikai /* teljesül-e */)
  axioma3(TVeremK,TLogikai /* teljesül-e */)
  axioma3_1(TVeremK,TLogikai /* teljesül-e */)
  axioma3H(TVeremK,TLogikai /* teljesül-e */) /* hiba-axióma */
  axioma3H_1(TVeremK,TLogikai /* teljesül-e */)
  axioma4(TVeremK,TLogikai /* teljesül-e */)
  axioma4_1(TVeremK,TLogikai /* teljesül-e */)
  axioma4_2(TVeremK,TLogikai /* teljesül-e */)
  axioma4H(TVeremK,TLogikai /* teljesül-e */) /* hiba-axióma */
  axioma4H_1(TVeremK,TLogikai /* teljesül-e */)

  ellenorzes1(TVeremK)
  ellenorzes2(TVeremK)
  ellenorzes3(TVeremK)
  ellenorzes3H(TVeremK)
  ellenorzes4(TVeremK)
  ellenorzes4H(TVeremK)

Clauses

  ures(verem([],def)).
  uresE(verem(_,nemDef),_). /* ImpErrAx */
  uresE(verem([],def),igaz).
  uresE(verem([_],def),hamis).
  uresE(verem([_|_],def),hamis).

  teto(verem(V,nemDef),_,verem(V,nemDef)). /* ImpErrAx */
  teto(verem([E|V],def),E,verem([E|V],def)). /* PP */
  teto(verem([],def),_,verem([],nemDef)). /* PP: Err */

  verembe(verem(V,nemDef),_,verem(V,nemDef)). /* ImpErrAx */
  verembe(verem(V,def),E,verem([E|V],def)). /* PP */

  verembol(verem(V,nemDef),_,verem(V,nemDef)). /* ImpErrAx */
  verembol(verem([E|V],def),E,verem(V,def)).  /* PP */
  verembol(verem([],def),_,verem(_,nemDef)).  /* PP: Err */

  axioma1(V,igaz) if
    axioma1_1(V,igaz).
  axioma1(V,igaz) if
   
axioma1_2(V,igaz).
  axioma1(_,hamis).

   axioma1_1(V,igaz) if
     ures(V) and uresE(V,igaz).
   axioma1_2(V,igaz) if
     not(ures(V)) and not(uresE(V,igaz)).

  axioma2(V,igaz)if
    axioma2_1(V,igaz).
  axioma2(_,hamis).
   axioma2(V,igaz) if
     verembe(V,_,verem(W,def)) and ! and
     uresE(verem(W,def),hamis).

  axioma3(V,igaz)if
    axioma3_1(V,igaz).
  axioma3(_,hamis).
  axioma3_1(V,igaz) if
    verembe(V,E,W) and ! and teto(W,E,W)

  axioma3H(verem(_,def),igaz)if
   
axioma3H_1(verem(_,def),igaz).
  axioma4H(_,hamis).
   axioma3H_1(verem(_,def),igaz) if
     uresE(verem(_,def),igaz) and ! and
    
teto(verem(_,def),_,verem(_,nemDef)
    or
     uresE(_,hamis).

  axioma4(V,igaz) if
    axioma4_1(V,igaz) and axioma4_2(V,igaz).
  axioma4(_,hamis).
   axioma4_1(V,igaz) if
     verembe(V,E,W) and ! and verembol(W,E,V).
   axioma4_2(V,igaz) if
     uresE(V,hamis) and ! and verembol(V,E,W) and ! and verembe(W,E,V)
    or
     uresE(V,igaz).

  axioma4H(verem(_,def),igaz) if
    axioma4H_1(verem(_,def),igaz).
  axioma4H(_,hamis).

   axioma4H_1(verem(_,def),igaz) if
     uresE(verem(_,def),igaz) and ! and
    
verembol(verem(_,def),_,verem(_,nemDef)
    or
      uresE(_,hamis).

  ellenorzes1(Teszt) if
   write("1:'",Teszt,"'-re") and
   axioma1(Teszt,JoE) and
   write("?: ",JoE) and nl.

  ellenorzes2(Teszt) if
   write("2:'",Teszt,"'-re") and
   axioma2(Teszt,JoE) and
   write("?: ",JoE) and nl.

  ellenorzes3(Teszt) if
   write("3:'",Teszt,"'-re") and
   axioma3(Teszt,JoE) and
   write("?: ",JoE) and nl.

  ellenorzes4(Teszt) if
   write("4:'",Teszt,"'-re") and
   axioma4(Teszt,JoE) and
   write("?: ",JoE) and nl.

  ellenorzes4H(Teszt) if
   write("4H:'",Teszt,"'-re") and
   axioma4H(Teszt,JoE) and
   write("?: ",JoE) and nl.

Goal

  clearwindow and

  /* 1. axióma tesztje: */

  /*(üres,def)-re igaz-e:*/
  V01=verem([],def) and ellenorzes1(V01)
and
  /*(nem üres,def)-re igaz-e:*/
  V1=verem([_],def) and ellenorzes1(V1)

and ! and /* 2. axióma tesztje: */

  /*(üres,def)-re igaz-e:*/
  V02=verem([],def) and ellenorzes2(V02)
and
  /*(nem üres,def)-re igaz-e:*/
  V2=verem([_],def) and ellenorzes2(V2)

and ! and /* 3. axióma tesztje: */

  /*(üres,def)-re igaz-e:*/
  V03=verem([],def) and ellenorzes3(V03)
and
  /*(nem üres,def)-re igaz-e:*/
  V3=verem([_],def) and ellenorzes3(V3)

and ! and /* 4. axióma tesztje: */

  /*(üres,def)-re igaz-e:*/
  V04=verem([],def) and ellenorzes4(V04)
and
  /*(nem üres,def)-re igaz-e:*/
  V4=verem([_],def) and ellenorzes4(V4)

and ! and /* 4H. axióma tesztje: */

  /*(üres,def)-re igaz-e:*/
  V04H=verem([],def) and ellenorzes4H(V04H)
and
  /*(nem üres,def)-re igaz-e:*/
  V4H=verem([_],def) and ellenorzes4H(V4H)

 



[1]  Szokás az irodalomban ezt P/P-specifikációnak is nevezni, mivel az pre- és postcondition, vagyis az elő- és az utófeltétel megadását jelenti.

[2] Egy másik elképzelés szerint az adott típushoz tartozó alaphalmazt terjesztik ki egy speciális, virtuális elemmel (error), s az axiómák között explicite szerepeltetik azokat az axiómákat, amelyek az inkorrekt használat „eredménytelenségét”  (error értékre vezetését) jelentik ki. [KV2001]

[3] Megjegyzések:

o        Az InputParaméterek lehetnek típust jellemző konstansok, de –típuskonstrukció esetén– típusok is. (Szokás e paramétereket ’generic parameter’-nek nevezni.)

o        A modulfej egyben a felhasználás „mintájául” is szolgál. Például: ExportModul Tömb(Típus TIndex: Típus TElem) Þ Típus Vektor=Tömb(1..9:Egész)

[4] Az algebrai leírásnál az Üres operációnak nincs értelmezési tartománya, mivel konstrukciós művelet; így –el­vileg– értelmetlen a „bemenettel” szemben bármiféle feltételt megfogalmazni. Ha mégis ellenőrizni kívánjuk a korrekt használatot, akkor „elszakadva” az algebrai leírástól, kimondhatunk előfeltételt. Az ellenőrzésre ok lehet a többszörös „inicializálás” célú felhasználás megakadályozása. Tudnunk kell, hogy a megvalósítással szemben komoly elvárást fogalmazunk meg ezzel: egy, minden „legális” értéktől megkülönböztethető érték és egy olyan automatizmus meglétét, amely az adatok ab ovo állapotára és annak létrehozására vonatkozik.

[5] A [KV2001] kettős specifikációról beszél a mienkétől némileg eltérő értelemben. Absztrakt és konkrét spe­cifikációt egyesítenek a kettős specifikációban, amelyek elsősorban az ábrázolás szintjében, s nem az elvárás-megfogalmazás nyelvezetében térnek el egymástól. (Bár legtöbbször óhatatlanul a nyelvezet-váltással is jár a konkretizálás.)

[6] Meggondolandó: ki kell-e jelenteni a változatlan komponensek változatlanságát!

[7] A most elemzendő példánkban nem fogunk adatbázisokat használni. Mivel a következő példánkhoz és mód­szer-változathoz már szükséges lesz, ezért teszünk említést erről a Prolog-szekcióról.

[8] Azt a döntést, hogy a verem valamifajta sorozat, még halaszthattuk volna. E pillanatban még nincs szerepe.

[9] Egyébként ugyanez a magyarázata annak, hogy az export modulban némely függvény paraméterlistáján a verem változóként szerepel.

[10] Sőt az algebrai rendszerben le sem írható ilyen szituáció. Ezért azután össze sem vethető e tekintetben a kétféle leírás. Már ez is jelzi, hogy valójában nem ekvivalens átalakítást jelent az „algebrai ® P/P” áttérés.

[11] Egy ilyen sorozat-kezelő betétet tartalmaz az A. függelék.

[12] Valójában lesz még további probléma is. Például a kvantorokat is tartalmazó formulák kódolása. Ezzel később foglalkozunk. Most csak egy megoldási ötletre utalunk: indukcióval helyettesítjük, azaz egyetlen elemére teljesülést foglaljuk először szabályba, majd olyan szabályt illesztünk hozzá, amely a további elemekre való teljesülést induktíve állítja.

[13] Végrehajtása után garantáltan Def állapotba kerül a struktúra, miközben visszaadott értéke a struktúra korábbi Def/NemDef állapota. Valahogy így:

    Szignatúra: Hibás?(Verem): Verem x Logikai

    Axióma: V=NemDef Þ Hibás?(V)=(V.Verem,Igaz) Ù V¹NemDef.

[14] Elképzelhető, hogy a fő struktúrára vagy csak input, vagy csak output értelemben van szükség. Ekkor a paraméter listán csak egyszer kell szerepeljen.