Creative Commons License
This work is licensed under a
Creative Commons Attribution-NonCommercial-ShareAlike 4.0
International License
.

Editor:

John Val

Bron: Learn Prolog Now!

Definite Clause Grammar Toevoegen recursie regels Een DCG voor een formele taal

Wat leer je in dit hoofdstuk:

Definite Clause Grammar

Voorbeeld 1: De eerder gebruikte
eenvoudige grammatica in DCG vorm

s --> np,vp.
np --> det,n.
vp --> v,np.
vp --> v.
det --> [een].
det --> [de].
n --> [hond].
n --> [vrouw].
v --> [aait].

Wat zijn Definite Clause Grammars? Simpel gezegd, een mooie manier om grammatica's te schrijven dat de onderliggende verschil lijsten verbergt. we bekijken drie voorbeelden.

Het eerste voorbeeld hiernaast is code die je direct in Swish kunt kopiëren en runnen met de vraag:

s([de,vrouw,aait,een,hond],[]).

Deze vraag is dezelfde als in het efficiënte voorbeeld en je de traces vergelijkt zie je geen verschil. Prolog heeft voor ons dus al het meest efficiënte voorbeeld als een standaard geïmplementeerd, met de zeer plezierige mogelijkheid om de grammatica's op een zeer eenvoudige wijze op te schrijven.

De vraag ?- listing(s). geeft:

s(A, B) :-
    np(A, C),
    vp(C, B).
true.
Zoals je ziet is dit precies de implementatie die we in het vorige hoofdstuk hebben gemaakt.

De vraag ?- s(X,[]) levert weer alle mogelijke zinnen die aan de grammatica voldoet en
de vraag ?- np(X,[]) levert weer alle mogelijke noun phrases.

Toevoegen recursie regels

Voorbeeld 2: Uitbreiding van de grammatica naar een
oneindig aantal regels.

s → s conj sEen zin impliceert (is opgebouwd uit) een (bij)zin (s)
gevolgd door een conjunction (voegwoord) gevolgd door een (bij)zin.
s → np vpEen zin impliceert (is opgebouwd uit) een noun phrase gevolgd door een verb phrase.
np → det nEen noun phrase impliceert een determiner
gevolgd door een noun.
vp → v np verb phrase impliceert een verb gevolgd door een noun phrase.
vp → vOf verb phrase impliceert een verb .
det → eenEen determiner impliceert het lidwoord een.
det → deOf een determiner impliceert het lidwoord de.
n → hondEen noun impliceert het zelfstandige naamwoord hond.
n → vrouwOf een noun impliceert het zelfstandige naamwoord vrouw.
v → aaitEen verb impliceert de werkwoordsvorm aait.
conj → enEen conjunction impliceert het voegwoord en.
conj → ofOf een conjunction impliceert het voegwoord of.
conj → maarOf een conjunction impliceert het voegwoord maar.

Een eerste implementatie in Prolog
van deze uitbreiding

s --> s, conj, s.
s --> np,vp.
np --> det,n.
vp --> v,np.
vp --> v.
conj --> [en].
conj --> [of].
conj --> [maar].
det --> [een].
det --> [de].
n --> [hond].
n --> [vrouw].
v --> [aait].

Zinnen kunnen vaak samengevoegd worden. Onze simpele grammatica kan slechts 20 zinnen produceren. Het is echter heel eenvoudig om met een heel kleine uitbreiding deze simpele grammatica oneindig veel zinnen te laten genereren. We voegen daartoe aan onze grammatica de volgende regels toe.

s → s conj s
conj → en
conj → of
conj → maar

De complete grammatica is rechts weergegeven. De regel s → s conj s houdt de bewering in dat een zin bestaat uit de samenvoeging van twee zinnen door een in het midden geplaatst voegwoord (conjunction (conj) ). Er zijn natuurlijk meer voegwoorden in de Nederlandse taal, we geven slechts een paar voorbeelden (en of en maar).

Het lijkt dat dit, in navolging van de implementatie van onze kleine grammatica, eenvoudig om deze regels om te zetten met als resultaat het programma hiernaast. Helaas zijn er grote problemen met deze eerste poging.

Wat gaat er mis? Laten we eens afvragen wat Prolog met deze DCG precies doet. Wat gebeurt er als we de vraag ?- s([de,vrouw,aait],[]) stellen? Het antwoord Prolog schiet gelijk in een oneindige lus.

Snap je waarom? Prolog vertaalt de DCG in gewone Prolog regels. Als we de recursieve regel s --> s, conj, s. zoals in het voorbeeld programma voor de niet recursieve regel s --> np,vp. dan wordt de vertaling de volgorde:

 
s(A,  B)  :-
	s(A,  C),
	conj(C,  D),
    s(D,  B).
   
s(A,  B)  :-
	np(A,  C),
	vp(C,  B).

Vanuit de declaratie van regels is dit oké, maar als dit moet worden uitgevoerd gaat het helemaal mis. Als Prolog de eerste regel gaat gebruiken, treft het gelijk het doel s(A,C) wat Prolog er toe zet om gelijk weer de eerste regel aan te roepen, wat weer lijdt tot de aanroep van s(A,C), waarna het weer de eerste regel aanroept, en zo voorts. Kortom we zitten vast in een oneindige lus.

Helpt het dan om de volgorde om te draaien? We beschouwen nu

 
s  -->  np,vp.
s --> s, conj, s.
	
met vertaling

 
s(A,  B)  :-
	np(A,  C),
	vp(C,  B).

s(A,  B)  :-
	s(A,  C),
	conj(C,  D),
    s(D,  B).
	

Nu wordt altijd eerst de niet recursieve regel aangeroepen. Wat gebeurt er nu met de vraag ?- s([de,vrouw,aait],[]).? Op deze vraag komt een positief resultaat, maar dit gaat helaas niet wanneer we een negatief resultaat verwachten. Als we de foutieve vraag ?- s([vrouw,aait],[]). stellen geeft de niet recursieve regel het antwoord false, waarna de tweede regel wordt aangeroepen, dan gelijk weer de eerste regel aanroept, die dan door de fout weer de tweede regel aanroept, dus weer een oneindige lus.

Een DCG voor samengestelde zinnen zonder
oneindige lussen, maar wel met de mogelijkheid
om een oneindig aantal zinnen te genereren.

s --> simpele_s.
s --> simpele_s, conj, s.
simpele_s --> np,vp.
np --> det,n.
vp --> v,np.
vp --> v.
conj --> [en].
conj --> [of].
conj --> [maar].
det --> [een].
det --> [de].
n --> [hond].
n --> [vrouw].
v --> [aait].

Toch is er een weg uit dit probleem. De standaard oplossing in dit soort gevallen is het introduceren van een nieuwe tussenregel. We kunnen bijvoorbeeld kiezen voor de regel simpele_s voor niet samengestelde zinnen. De DCG ziet er dan uit als het voorbeeld hiernaast.

Natuurlijk controleer je nu gelijk dat Prolog niet meer in een oneindige lus terecht komt. Het programma loopt dus niet meer vast, wat vanuit het gezichtspunt van een programmeur veel voldoening geeft. Vanuit taalkundig oogpunt is dit minder attractief. De DCG met de oneindige lus was vanuit taalkundige intuïtie beter te begrijpen. De toevoeging van simpele_s heeft helaas geen taalkundige reden.

De moraal van dit verhaal. Definite Clause Grammar's in Prolog zijn niets magisch. Ze bieden de mogelijkheid om een grammatica makkelijker op te schrijven, maar het is niet altijd mogelijk om precies de grammatica te implementeren. Bekijk goed wat Prolog met een een regel doet, vooral als er recursie in het spel is.

Een DCG voor een simpele formele taal

De context vrije grammatica voor an bn

s → ∅
s → l s r
l → a
r → b

Ons laatste voorbeeld van een DCG is een DCG voor een formele taal. Een formele taal is geen natuurlijke taal (de taal waarmee mensen communiceren), maar meestal een verzameling van tekenreeksen die wetenschappers binnen de informatica, logica en wiskunde gebruiken voor verscheidene doeleinden (b.v. programmeertalen).

Een simpel voorbeeld van een formele taal is de taal bestaande uit de woorden an bn, met n een natuurlijk getal (0,1,2,3,...). De woorden bestaan dus uit tekenreeksen waarin een gelijk aantal a's en b's in voorkomen en wel zo dat er eerst een ononderbroken reeks van a's is die wordt gevolgd door en even lange ononderbroken reeks van b's bijvoorbeeld ab, aabb, aaabbb en aaaaaaabbbbbbb. Echter abba en aaabbbaabb voldoen niet hoewel deze evenveel a's als b's bevatten.

De context vrije grammatica die deze formele taal genereert is hiernaast weergegeven. Het symbool staat voor in het geheel geen woord ofwel het empty (=lege) woord. Dus s kan een leeg woord zijn (eerste regel) of een woord dat gevormd is uit een linkerzijde l een eerder gevormd woord s en een rechterzijde r. De linkerzijde verwijst naar een letter a en de rechterzijde naar een b. Het zou duidelijk moeten zijn dat deze grammatica alle en alleen elementen van an bn genereert, inclusief het lege woord.

De DCG voor an bn

s --> [].
s --> l, s, r.
l --> [a].
r --> [b].

Deze context vrije grammatica is simpel om te zetten naar de DCG die hiernaast is weergegeven. De tweede regel is recursief maar, omdat l en r geen oneindige lus genereren, geeft deze geen probleem. De DCG werkt precies zo als we willen. Stel maar eens de vragen:

  1. ?- s([a,a,a,b,b,b],[]).
  2. ?- s([a,a,a,b,b,b,b],[]).
  3. ?- s(X,[]).
Zoals verwacht geeft de eerste vraag true als antwoord, de tweede false en de derde loopt alle woorden in de taal af te beginnen met de lege verzameling.