Tanácsok Prolog programok írásához

  1. Program-formátum.

    Minden eljáráshoz adjunk meg egy fejkommentet, amely leirja milyen logikai összefüggést valósit meg az eljárás. Próbáljunk a paraméterek bemenő/kimenő jellegétől elvonatkoztatni, azaz csak az eljárás által megvalósitandó reláció jellemzőit leírni.

    A fejkomment alakja:

    <eljárásfej>: <egy vagy több kijelentő mondat, amelyben az összes argumentum szerepel>

    Ha vannak megszorítások a paraméterek bemenő/kimenő jellegére, azt külön mondatban írjuk le, vagy az SWI kézikőnyvben/help-rendszerben használt jelőléssel a paraméter elé írt jelekkel adjuk meg (+ = bemenő, - = kimenő, ? = be- és kimenő) Például:

    % osszege(+SzamLista, +Osszeg0, ?Osszeg): Osszeg megegyezik SzamLista % és Osszeg0 összegével. osszege([], Ossz, Ossz). osszege([Szam|SzamL], Ossz0, Ossz):- Ossz1 is Ossz0+Szam, osszege(SzamL, Ossz1, Ossz).

    Ha az eljárás egy olyan klózzal kezdődik, amelyben minden fej-argumentumot egy (megnevezett) változó jelöl, akkor a fenti alakban nem szükséges az <eljárásfej> megadása, és a kezdő klózfej argumentumneveire hivatkozhatunk a fejkommentben. Példa:

    % Az 1*1, 2*2, ..., N*N számok összege S. nosszeg(N, S):- findall(I2, (between(1, N, I), I2 is I*I), I2L), osszege(I2L, 0, S).

    Az egy eljáráshoz tartozó klózokat között ne hagyjunk üres sort. Ha az egyes klózokhoz kommentárt kívánunk fűzni, akkor ezt a klózfej vagy a hívás után a jobb szélen tegyük. Eljárások közé mindig tegyünk legalább egy üres sort.

    Az egy eljáráshoz tartozó klózok mindig folyamatosan egymás után következzenek, közéjük ne szúrjunk be idegen eljárásokat.

  2. Típushibák

    Ebben a részben néhány típushibára hívjuk fel a figyelmet.

  3. Változónevek

    Mindig olyan változóneveket használjunk amelyek a változó jelentésével asszociálhatóak. Lehetőleg kerüljük az A, B, C, X, Y, Z, stb. változóneveket. Egy hasznos konvenció a következő: ha egy listaelem jelölésére a V változónevet használjuk, akkor az ebből képzett lista jelölésére használjuk a VL vagy VLista (angol nyelven írt programban a Vs) jelölést.

    Az egy klózban egyszer előforduló változókat mindig aláhúzással, vagy aláhúzással kezdődő változónévvel jelöljük. Aláhúzással kezdődő változónevet ne használjunk többször egy klózban.

  4. A Prolog programozás stílusa

    Használjuk ki a Prolog egyszerűsitő jelöléseit, pl. [A|[B|C]] helyett mindig írjuk a [A,B|C] kifejezést!

    A diszjunkciók túlzott használata áttekinthetetlenné teszi a programot. Ezért lehetőleg ne használjunk diszjunkciót! Gondoljuk meg, nem vezet-e be a diszjunkció felesleges többszörös megoldásokat.

    Az =/2 beépített eljárás használata általában kerülendő. Az = eljárás hívásai többnyire kiküszöbölhetők úgy, hogy az egyik oldalon szereplő változó minden előfordulását a másik oldalra cseréljük.

    Például:

    % eleje(+KezdoLista, +TeljesLista): A KezdoLista lista megegyezik a % TeljesLista egy kezdőszeletével. eleje([K|KL], [T|TL]):- K = T, eleje(KL, TL). eleje([], _). Ebben az eljárásban az első klózt célszerűbb így irni: eleje([K|KL], [K|TL]):- eleje(KL, TL). Az =/2 eljárás használatára szükség lehet diszjunkciókban, illetve vágót tartalmazó klózokban .

  5. Speciális esetek

    Sokszor nagy a csábítás arra, hogy egy speciális esetre, amikor az eljárás esetleg egyszerübben is végrehajtható, egy külön klózt írjunk. Ez azonban a programot kevésbé áttekinthetővé teszi és ugyanannak a megoldásnak többszöri előállítását is eredményezheti. Ez az utóbbi jelenség rendkívül veszélyes, mert nagy lassulásokat okozhat, kereső jellegű programoknál.

    Ezért azt javasoljuk, hogy kerüljük a speciális esetek külön kezelését! Példa:

    % append(L1, L2, L3): Az L1 és L2 listák egymásutánfűzése adja L3-t. append([], L, L). append([X], L, [X|L]). % Vigyázat: felesleges klóz append([X|L1], L2, [X|L3]):- append(L1, L2, L3). A fenti append eljárás a megoldások többségét kétszer fogja kiadni. Egy vágó beszúrásával ezen segíthetünk, de akkor megszűnik az append visszafelé való használhatósága. Egy másik javítási lehetőség az, hogy az append utolsó klózában egyszerre két elemet emelünk át az első és harmadik argumentum között, amivel persze a második klóz megszűnik felesleges lenni.

  6. Megoldások többszörös előállítása

    Egy az előzővel rokon problémaforrás: vigyázzunk, hogy minden megoldást csak egyszer állítsunk elő. Tipikus hiba, hogy a rekurzió leállító feltételét egy külön klózban is, és a rekurzív klózba tett diszjunkcióban is vizsgáljuk. Ez egy algoritmikus, vagy funkcionális nyelvnél nem hiba, csak felesleges és hatékonyságcsökkenést jelent. Prologban viszont az adott eljárás többszörösen fog sikerülni, ami többszörös megoldások megjelenését és hatványozott lassulást okozhat. Példa:

    % vege(?Veg, +Teljes): Veg a Teljes lista egy végszelete vege(VL, VL). vege(VL, [_|TL]):- ( VL = TL % ez a feltétel felesleges ; vege(VL, TL) ). A megoldások többszörös megjelenését mutatja az alábbi hívás: 3 ?- findall(V, (vege(V,[a,b,c]), vege(V,[a,c])), S). /*közös végeket keresünk*/ S = [[c],[c],[c],[c],[],[],[],[]]

  7. Vágó használata

    Először mindenképpen próbáljunk meg megoldást találni vágó használata nélkül. Ezután, ha hatékonysági okokból fontosnak tartjuk a vágó bevezetését, akkor nagyon alaposan gondoljuk meg, hogy hová helyezzük a vágót a klózban. Nagyon gyakran szükség lehet a fejbeli egyesítéseknek explicit egyenlőségekké való alakítására, hogy ezek a vágó utánra kerüljenek. Például tekintsünk először egy vágó nélküli megoldást egy egyszerű problémára.

    % duplazott(+SL, +DL): a DL lista úgy áll elő az SL számlistából, hogy a % pozitív elemeket megduplázzuk. duplazott([], []). duplazott([A|SL], [A, A|DL]):- A > 0, duplazott(SL, DL). duplazott([A|SL], [A|DL]):- A =< 0, duplazott(SL, DL). Ha ebben A előjelének kétszeres vizsgálatát ki akarjuk küszöbölni vágó felhasználásával, akkor első kisérletként az alábbi hibás definiciót kapjuk: % duplazott(+SL, +DL): a DL lista úgy áll elő az SL számlistából, hogy a % pozitív elemeket megduplázzuk. HIBÁS MEGOLDÁS! duplazott([], []). duplazott([A|SL], [A, A|DL]):- A > 0, !, duplazott(SL, DL). duplazott([A|SL], [A|DL]):- duplazott(SL, DL). A fenti megoldás hibás, mert a ?-duplazott([1], [1]). hívás sikeresen lefut. A helyes megoldás: % duplazott(+SL, +DL): a DL lista úgy áll elő az SL számlistából, hogy a % pozitív elemeket megduplázzuk. duplazott([], []). duplazott([A|SL], DL):- A > 0, !, DL = [A, A|DL1], duplazott(SL, DL1). duplazott([A|SL], [A|DL]):- duplazott(SL, DL).


Megjegyzéseket a szeredi@iqsoft.hu címre várunk.

Utoljára Szeredi Péter módosította 1995. december 5-én.