Prolog på villspor: Fire vanlige tabber i logisk programmering
Fire år med logikkprogrammering som kan gå galt
Prolog har en sær tiltrekning. Mens de fleste utviklere velger imperative språk og objektorientering, holder Prolog-folk fast ved en helt annen måte å tenke på. Det føles både elegant og annerledes – helt til koden plutselig ikke lenger virker som den skal.
Noen få grunnprinsipper skiller god Prolog fra kode som skjult faller fra hverandre i produksjon. Bryter du dem, risikerer du resultater som enten er feil, mangler løsninger eller blir umulige å teste. Her er de fire klassiske fellene.
1. Cut som dreper løsninger
Når en predikat fungerer fint med konkrete verdier, men svikter når noen stiller et mer generelt spørsmål, ligger ofte en cut (!) eller en if-then-else (->) bak. De samme mekanismene som gjør koden «raskere» eller «enklere» for deg, gjør den også mindre generell.
factorial(0, 1) :- !.
factorial(N, F) :-
N > 0,
N1 is N - 1,
factorial(N1, F1),
F is N * F1.
Når du spør ?- factorial(N, F). får du bare én rad. Alt annet forsvinner, fordi cut-en blokkerer backtracking. Bedre er det å bruke dif/2 og høyereordens predikater. Da holder koden seg ren og testbar.
2. Mutering av databasen
Når mange begynner å bruke assertz/1 og retract/1, føler de seg som en slags magiker. De kan endre kunnskapsbasen mens programmet kjører. Men hver gang du muterer globalt, bygger du opp usynlige avhengigheter.
Tilstand som ikke flyter gjennom argumenter, men ligger skjult i databasen, gjør både testing og feilsøking til et mareritt. Hvert testløp kan etterlate spor som ødelegger neste kjøring. Løsningen er enkel: send tilstanden gjennom argumenter eller bruk kontekstnotasjon.
3. Blanding av logikk og utskrift
solve_and_print :-
solution(S),
format("The solution is: ~q~n", [S]).
En slik blanding gjør det umulig å teste eller gjenbruke predikatet. Du får kun utskrift på skjermen, ikke et Prolog-term du kan jobbe videre med. Separat logikk og presentasjon: la predikatet beskrive løsningen, mens øverste nivå tar seg av formatet.
4. Gamle aritmetikk-predikater
Mange holder fortsatt fast ved (is)/2, (=:=)/2 og (>)/2 fordi «det har alltid fungert». Men de siste tjue årene har Constraint Logic Programming over Finite Domains (CLP(FD)) gjort det mulig å skrive koden som en ren spesifikasjon.
Low-level-konstruktene blander deklarativ intensjon med operasjonelle detaljer – en belastning spesielt for nybegynnere. Med CLP(FD) blir intensjonen tydelig og koden lettere å lese.
Når alt går galt på én gang
horror_factorial(0, 1) :- !.
horror_factorial(N, F) :-
N > 0,
N1 is N - 1,
horror_factorial(N1, F1),
F is N * F1.
Denne versjonen bryter flere prinsipper samtidig: cut ødelegger backtracking, lavnivå-aritmetikk krever korrekt instantiert input, og koden fungerer bare som funksjon – ikke som relasjon. Når du spør etter alle gyldige kombinasjoner av N og F, får du bare én løsning.
Slik skriver du bedre Prolog
- Bruk deklarative mønstre og høynivå-verktøy fremfor cut og type-sjekk.
- Hold tilstanden synlig – send den gjennom argumenter.
- Skill logikk fra presentasjon.
- Bytt til CLP(FD) og moderne abstraksjoner.
Når Prolog-koden din svarer på det mest generelle spørsmålet og leses som en ren spesifikasjon, får du den sanne kraften av logikkprogrammering. Da blir både utvikling og vedlikehold langt mer behagelig.