%! Author = tdpeuter
%! Date = 27/03/2025

\documentclass[11pt,a4paper]{article}

\usepackage{amsmath}
\usepackage[dutch]{babel}         % Nederlands taal
\usepackage[style=apa]{biblatex}  % Bronnen
\usepackage{enumitem}             % Aanpasbare lijsten
\usepackage{float}                % Figures
\usepackage[margin=2cm]{geometry} % Sane marges
\usepackage{graphicx}             % Figures
\usepackage{hyperref}             % Hyperlinks
\usepackage{minted}               % Syntax highlighting
\usepackage{multicol}             % Meerdere kolommen
\usepackage{url}                  % Beter geformatteerde URLs

\addbibresource{bibliography.bib}

\title{Ghent Prolog}
\author{Tibo De Peuter}
\date{\today}

\begin{document}
    \maketitle

    \abstract{
    % KERN: Wat is Ghent Prolog?
        Ghent Prolog is een command-line Prolog interpreter geschreven in Kotlin.
        Het biedt een subset van de functionaliteit van SWI-Prolog, waaronder database operaties en meta abstracties.
        Hoewel het programma nog niet als equivalent van SWI-Prolog kan worden beschouwd, is Ghent Prolog een nuttig leerproject.
    }


    \section{Overzicht}\label{sec:overzicht}

    % KERN: Insteek
    Ghent Prolog is een proof of concept van een Prolog interpreter, als alternatief voor SWI-Prolog.
    Het volgt grotendeels ~\cite[de ISO Prolog standaard]{iso1995iec}, maar is niet volledig compatibel.

    % KERN: Programmeertaal, libraries, etc.
    Ghent Prolog werd geschreven in Kotlin en maakt gebruik van zowel object-gerichte als functionele concepten.
    Er werden twee bibliotheken gebruikt:
    \href{https://github.com/h0tk3y/better-parse}{\texttt{better-parse}}, voor het parsen van een gegeven grammatica, en
    \href{https://github.com/xenomachina/kotlin-argparser}{\texttt{kotlin-argparser}}, voor het gebruik van command-line argumenten.

    % KERN: Hoe ziet Ghent Prolog er abstract uit?
    De verschillende fases van een interpreter (lexing, parsing, evaluatie, etc.) zijn in Ghent Prolog zo goed mogelijk gescheiden.
    Op die manier blijft de implementatie modulair en uitbreidbaar.

    De volgende secties concentreren zich op de evaluatie-fase van de interpreter.
    Voor meer informatie over de lexing en parsing, zie sectie~\ref{sec:lexing-parsing-preprocessing}.


    \section{Programma en REPL}\label{sec:programma-repl}

    % KERN: Methode
    Ghent Prolog's evaluatiemethode kan gezien worden als een vorm van \textit{backward chaining} (\cite{russell2016}), maar komt niet exact overeen met de definitie.

    % KERN: Datastructuur
    Ghent Prolog maakt gebruik van functie-oproepen en streams om een backtracking mechanisme te implementeren.
    Bijzondere logica met betrekking tot \textit{choicepoints} en foutenafhandeling maakt gebruik van de Kotlin \mintinline{kotlin}{Result} klasse.
    Om de resultaten van unificatie en evaluatie \textit{lazy} te verwerken, wordt er gebruik gemaakt van Kotlin \mintinline{kotlin}{Sequence}'s (\textit{lazy streams}).

    Meer specifiek wordt er gebruik gemaakt van de volgende types:

    \begin{minted}{kotlin}
        typealias Substitutions = Map<Term, Term>
        typealias Answer = Result<Substitutions>
        typealias Answers = Sequence<Answer>
    \end{minted}

    Een lege \mintinline{kotlin}{Sequence} betekent dat er geen oplossingen zijn gevonden.
    Een \mintinline{kotlin}{Result.success} met \mintinline{kotlin}{Substitutions} representeert een oplossing met de resulterende substituties (die leeg kan zijn).
    Een \texttt{Result.failure} betekent een fout of bijzondere logica die afgehandeld moet worden.

    Er werden twee interfaces gedefinieerd:

    \begin{minted}{kotlin}
        interface Resolvent {
            fun solve(goal: Goal, substitutions: Substitutions): Answers
        }

        interface Satisfiable {
            fun satisfy(substitutions: Substitutions): Answers
        }
    \end{minted}

    Klassen die \mintinline{kotlin}{Resolvent} implementeren kunnen een \textit{goal} oplossen, bijvoorbeeld een database of een regel (\textit{rule}).
    Klassen die \mintinline{kotlin}{Satisfiable} implementeren kunnen worden geëvalueerd, bijvoorbeeld operatoren.

    De verschillende Prolog termen werden voorgesteld als klassen op basis van
    \href{https://www.swi-prolog.org/pldoc/man?section=glossary}{SWI-Prolog's Glossary of Terms},
    met bijkomende inspiratie uit~\cite{deransart-1996}.


    \section{Evaluatiestrategie}\label{sec:evaluatiestrategie}

    % KERN: Evaluatiestrategie
    Ghent Prolog maakt gebruik van een depth-first zoekstrategie.

    Op het moment dat de databank een goal gevraagd wordt (\textit{query}), worden de volgende stappen doorlopen:

    \begin{enumerate}
        \item De databank geeft de goal beurtelings door aan de overeenkomende clauses, aan de hand van \mintinline{kotlin}{solve}.
        \item Voor elke clause wordt de goal met de head geünificeerd, wat nieuwe substituties kan introduceren.
        \item De body van de clause wordt geëvalueerd (\mintinline{kotlin}{Body.satisfy}), die zo de nieuwe goal wordt.
        Deze procedure wordt herhaald totdat de goal \mintinline{prolog}{true} of \mintinline{prolog}{false} is.
        \item Het programma zal backtracken naar de functie die de laatste stap uitvoerde, en de pas geïntroduceerde substituties omhoog doorgeven.
        Daar wordt de juiste logica uitgevoerd, of het resultaat wordt verder doorgegeven aan de volgende stap.
    \end{enumerate}

    Doorheen dit proces kunnen variabelen hernoemd worden aan de hand van \mintinline{prolog}{numbervars/1}.
    Een schematisch overzicht van dit proces is te zien in figuur~\ref{fig:renaming}.

    % TODO Verklarende figuur met substitutie toevoegen

    % Overzicht van geïmplementeerde predicaten in appendix.
    Operator-specifieke logica bevindt zich in de klassen van de operatoren zelf.

    \begin{figure}[H]
        \centering
        \includegraphics[width=0.4\textwidth]{renaming}
        \caption{Voorbeeld van een substitutie met variable renaming.}\label{fig:renaming}
    \end{figure}

    In de volgende subsecties worden de belangrijkste onderdelen van de evaluatiestrategie in meer detail besproken.

    \subsection{Unificatie}\label{subsec:unificatie}

    Het unificatie-algoritme is gebaseerd op Robinsons Unificatie-algoritme, zoals beschreven door~\cite{boizumault-1993}, met inspiratie van~\cite{russell2016}.
    Merk op dat er gebruik gemaakt wordt van de \textit{occurs check}.

    \subsection{Cut}\label{subsec:cut}

    De cut operator geeft altijd een \mintinline{kotlin}{Result.failure(AppliedCut)} terug wanneer \mintinline{kotlin}{satisfy} wordt opgeroepen.
    Deze uitzondering moet dan afgehandeld worden door de aanroepende functie, wat meestal resulteert in het teruggeven van de eerstvolgende oplossing en het onderbreken van de logica.

    \subsection{Meta abstracties}\label{subsec:meta-abstractions}

    % KERN: Wat wordt ondersteund?
    Ghent Prolog ondersteunt meta abstracts zoals \mintinline{prolog}{functor/3}, \mintinline{prolog}{arg/3}, \mintinline{prolog}{=../2} en \mintinline{prolog}{clause/2}.
    Daarnaast werden ook de operatoren \mintinline{prolog}{reset/3} en \mintinline{prolog}{shift/1} ingebouwd.
    Die laten toe om \textit{delimited continuations} te gebruiken.

    % KERN: Hoe wordt dat ondersteund?
    De ingebouwde ondersteuning van delimited continuations maakt opnieuw gebruik vaan een uitzondering.
    Deze keer heet die \mintinline{kotlin}{Result.failure(AppliedShift)}, die de nieuwe substituties, doorgespeeld term (\textit{ball}) en de nieuwe \textit{continuatie}.
    Net zoals bij de cut operator, moet deze uitzondering afgehandeld worden door de aanroepende functie.
    In dit geval zal de \mintinline{kotlin}{AppliedShift} steeds doorgespeeld worden, totdat de \mintlinline{kotlin}{Reset} de uitzondering opvangt.
    Daarna gaat het programma verder met de nieuwe substituties en continuatie.


    \section{Resultaat}\label{sec:resultaat}

    De implementatie van Ghent Prolog ondersteunt de gevraagde functionaliteit, waaronder database operaties en meta abstracties.
    De architecturele verschillen met SWI-Prolog zijn echter groot, wat zowel positieve als negatieve gevolgen heeft.

    % KERN: Uitbreidbaarheid
    \textbf{De architectuur van Ghent Prolog is modulair en uitbreidbaar.}
    Eens de evaluatiestrategie en datastructuren zijn gedefinieerd, is het eenvoudig om extra ingebouwde operatoren toe te voegen.
    Een volledig overzicht van de geïmplementeerde predicaten, waaronder extra's en uitbreidingen, kan teruggevonden worden in sectie~\ref{sec:predicaten}.

    % KERN: Nesting probleem
    \textbf{Code wordt snel onoverzichtelijk} door het gebruik van \mintinline{kotlin}{Sequence} en \mintinline{kotlin}{Result}.
    Belangrijke logica zit genest in onduidelijke boilerplate.
    De code in~\ref{lst:nesting} komt bijvoorbeeld in de meeste termen voor, weliswaar in verschillende vorm.

    \begin{listing}[H]
    \begin{minted}{kotlin}
        /* Function entry logic */
        unifyLazy(a, b, subs).forEach { firstResult ->
            firstResult.map { firstSubs ->
                /* First stage logic, e.g. preparing the body etc. */
                c.satsify(firstSubs).forEach { secondResult ->
                    secondResult.fold(
                        onSuccess = { secondSubs ->
                            /* End result logic */
                            yield(Result.success(firstSubs + secondSubs))
                        },
                        onFailure = { failure ->
                            when (failure) {
                                is AppliedCut   -> /* Cut logic */
                                is AppliedShift -> /* Shift logic */
        } } ) } } }
    \end{minted}
    \caption{Voorbeeld van geneste boilerplate code}\label{lst:nesting}
    \end{listing}

    % KERN: Overerving zorgt voor boilerplate
    \textbf{De implementatie bevat boilerplate code} door het gebruik van overerving en interfaces in de klassenrepresentatie van termen.
    Deze methode wordt gebruikt om de onderlinge relaties tussen de verschillende termen te beschrijven.
    Hoewel nuttig voor de representatie van de AST en generieke functies, moeten de meeste klassen de \mintinline{kotlin}{satsify} en \mintinline{kotlin}{solve} methoden overschrijven.
    Dit zorgt voor boilerplate code en verspreidt de logica over verschillende klassen.
    Daarnaast kan het leiden tot verlies van type-informatie wanneer generieke functies en type-specifieke functies worden gemengd.

    \subsection{Functionele afwijkingen van SWI-Prolog}\label{subsec:afwijkingen}
    % TODO Maak appendix?

    % KERN: Functionele afwijkingen
    De huidige versie van Ghent Prolog heeft functionele afwijkingen van SWI-Prolog:

    \begin{itemize}
        \item Door het gebruik van de \textit{occurs check} is het niet mogelijk om een oplossing te vinden voor recursieve unificatie.
        Voor \mintinline{prolog}{?- X = f(X).} vindt SWI-Prolog de oplossing \mintinline{prolog}{X = f(f(X))}, maar Ghent Prolog geeft \texttt{false} terug.
        \item Ghent Prolog maakt geen gebruik van SLD-resolutie, in tegenstelling tot ISO Prolog en SWI-Prolog (\cite{toman-2008}).
        \item In de REPL wordt er niet meteen teruggekeerd naar de prompt als een query maar één oplossing heeft.
        Dit is een artifact om oplossingen die de database aanpassen, bijvoorbeeld \mintinline{prolog}{retract/1}, pas uit te voeren wanneer de gebruiker dat expliciet vraagt.
        Het gebruik van een iterator en \mintinline{kotlin}{it.hasNext()} zou al de volgende oplossing genereren.

        Verder wordt ook de puntkomma (\texttt{;}) pas herkend na een newline.
        \item Voorlopig is het niet mogelijk om ingebouwde operatoren te overschrijven met \mintinline{prolog}{assert/1} of \mintinline{prolog}{retract/1}.
        Dit is een gevolg van het gebruik van de preprocessor.
    \end{itemize}


    \section{Toekomstig werk}\label{sec:toekomstig-werk}

    % KERN: Wat moet volgende keer anders?
    Bij de ontwikkeling van een variant van Ghent Prolog, wordt er best aandacht besteed aan de volgende zaken:

    \begin{itemize}
        \item Het gebruik van een stack voor choicepoints en andere informatie om het gebruik van exceptions weg te werken en eenvoudiger choicepoints te kunnen manipuleren.
        Zo kan bijvoorbeeld de cut operator transparanter \textit{committen} tot de huidige oplossing door in één stap de juiste choicepoints te verwijderen, zonder dat elke operator een \texttt{AppliedCut} moet afhandelen.
        \item Het gebruik van een visitor-patroon in plaats van overerving, om boilerplate te verminderen, zoals aangegeven in sectie~\ref{sec:resultaat}.
    \end{itemize}


    \section{Conclusie}\label{sec:conclusie}

    % KERN: Niet equivalent.
    Ghent Prolog is duidelijk een proof of concept project.
    Het kan nog lang niet als equivalent van SWI-Prolog beschouwd worden.

    % KERN: Wat heb ik eruit gehaald?
    Het implementeren ervan en het onderzoeken van de (voorlopig) ingebouwde functionaliteit biedt echter een goed inzicht in de werking van Prolog.
    Het geeft een beter begrip van de ingebouwde operatoren en hoe die interageren.

    Persoonlijk vond ik de moeilijkste stap het opzetten van de evaluatie-strategie.
    Daarna ging het toevoegen van extra operatoren en functionaliteit vrij vlot.
    Ik ben ondertussen bezig met het zoeken van een manier om een bronbestand te schrijven waarmee de som van de getallen in een interval berekend wordt, aan de hand van \mintinline{prolog}{read/1}, \mintinline{prolog}{between/3} en delimited continuations.

    \printbibliography

    \appendix
    \newpage


    \section{Lexing, parsing en preprocessing}\label{sec:lexing-parsing-preprocessing}

    % KERN: Overzicht
    Traditioneel zijn de lexer en parser aparte componenten.
    Om tijd te besparen werd echter voor het lexen en parsen van Prolog broncode en REPL-invoer gebruik gemaakt van de \texttt{better-parse} bibliotheek.
    Een gecompartimentaliseerde, onafgewerkt variant wordt besproken in sectie~\ref{subsec:onafgewerkte-lexer-parser}.

    % KERN: Grammatica
    De parser maakt gebruik van een vereenvoudigde Prolog grammatica, gebaseerd op
    \href{https://github.com/antlr/grammars-v4/blob/master/prolog/prolog.g4}{de ANTLR Prolog grammatica},
    \href{https://sicstus.sics.se/sicstus/docs/3.7.1/html/sicstus_45.html#SEC370}{SICStus Prolog Full Prolog Syntax} en
    \href{https://github.com/simonkrenger/ch.bfh.bti7064.w2013.PrologParser/blob/2c06e5a221c1cc51ba766304250749a7f0caed8c/doc/prolog-bnf-grammar.txt}{simonkrenger/PrologParser's BNF}.
    De uiteindelijk gebruikte grammatica kan teruggevonden worden in de \texttt{parser/grammars}.

    % KERN: Simpliciteit en preprocessor
    Omdat de lexer en parser niet op maat gemaakt zijn, worden ze bewust zo eenvoudig mogelijk gehouden.
    Hun verantwoordelijkheid is het omzetten van inputtekst naar een basis Abstract Syntax Tree (AST), die enkel uit generieke Prolog termen bestaat.
    Merk op dat de parser ook verantwoordelijk is voor operator precedentie en associativiteit, zoals besproken wordt in sectie~\ref{subsec:operator-precedence}.

    Daarna worden de termen in de AST door de preprocessor omgezet naar specifieke klassen die de ingebouwde operatoren voorstellen.
    Termen worden herkend aan de hand van hun functor en ariteit.

    Na de preprocessor is de AST klaar om gebruikt te worden door de evaluatie-fase.

    \subsection{Onafgewerkte Lexer en Parser implementatie}\label{subsec:onafgewerkte-lexer-parser}

    % KERN: Introductie vorige implementatie
    Origineel werden de lexer en parser op maat gemaakt, zonder het gebruik van een parser library.
    De lexer en parser waren gebaseerd op
    \href{https://craftinginterpreters.com/contents.html}{Crafting Interpreters, Robert Nystrom} en
    \href{https://www.youtube.com/playlist?list=PLGNbPb3dQJ_5FTPfFIg28UxuMpu7k0eT4}{Building a Parser from scratch, Dmitry Soshnikov}.
    De parser was een recursive descent parser.

    De voorlopige implementatie kan nog steeds teruggevonden worden op commit
    \href{https://github.com/Logisch-Programmeren/project2425-tdpeuter/tree/d5632e92173abf443251d2445de31f03b696b1d0/src}{d5632e9}.


    \section{Aanvullende opmerkingen}\label{sec:aanvullende-opmerkingen}

    \subsection{Operator precedentie en associativiteit}\label{subsec:operator-precedence}

    Ghent Prolog ondersteunt operator precedentie en associativiteit.
    Deze functionaliteit bevindt zich in de parser, omdat de argumenten van een operator steeds rechtstreeks als parameters in de constructor van de klasse worden meegegeven.

    Operator precedentie en associativiteit werd geïmplementeerd volgens de~\href{https://www.swi-prolog.org/pldoc/man?section=operators}{SWI-Prolog documentatie}.

    \subsection{Database bewerkingen}\label{subsec:database}

    Ghent Prolog laat toe om tijdens de programma-uitvoering database bestanden te laden met behulp van de \mintinline{prolog}{consult/1} predicaat.

    De ingeladen predicaten worden als \mintinline{prolog}{static} gemarkeerd, maar kunnen \mintinline{prolog}{dynamic} worden met behulp van het \mintinline{prolog}{dynamic/1} predicaat.
    Dit is analoog aan SWI-Prolog.

    \subsection{Test driven development}\label{subsec:ttd}

    Doorheen de ontwikkeling van grote delen van mijn implementatie heb ik gebruik gemaakt van Test Driven Development, onder andere met behulp van \href{https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-t-o-d-o.html}{Kotlin \texttt{TODO}}.


    \section{Uitvoeren en testen}\label{sec:uitvoeren-en-testen}

    Om Ghent Prolog op een Windows, Linux of MacOS uit te voeren is het voldoende om Java te installeren en Ghent Prolog op te roepen met \texttt{./src/gpl}.
    De nodige stappen, waaronder het bouwen van een JAR, worden dan automatisch uitgevoerd.

    De ingediende JAR kan ook handmatig opgeroepen worden met \texttt{java -jar ./build/gpl.jar}.

    \subsection{Testen}\label{subsec:testen}

    De testen kunnen uitgevoerd worden door de meeste IDE's.

    Alternatief kunnen de testen uitgevoerd worden met \texttt{./gradlew test}.


    \section{Overzicht van geïmplementeerde predicaten}\label{sec:predicaten}

    Deze sectie geeft een overzicht van de geïmplementeerde predicaten in Ghent Prolog, gesorteerd volgens de categorieën die in
    \href{https://www.swi-prolog.org/pldoc/man?section=builtin}{de SWI-Prolog documentatie}
    gebruikt worden.

    \begin{multicols}{2}
        \begin{itemize}[label={}]
            \item \textbf{Analysing and Constructing Terms}
            \begin{itemize}
                \item \texttt{functor/3}
                \item \texttt{arg/3}
                \item \texttt{=../2}
                \item \texttt{numbervars/1}
                \item \texttt{numbervars/3}
            \end{itemize}
            \item \textbf{Arithmetic}
            \begin{itemize}
                \item \texttt{between/3}
                \item \texttt{succ/2}
                \item \texttt{plus/3}
                \item \texttt{=\textbackslash=/2}
                \item \texttt{=:=/2}
                \item \texttt{is/2}
                \item \texttt{-/1}
                \item \texttt{+/1}
                \item \texttt{+/2}
                \item \texttt{*/2}
                \item \texttt{//2}
                \item \texttt{inf/0}
            \end{itemize}
            \item \textbf{Comparison and Unification of Terms}
            \begin{itemize}
                \item \texttt{=/2}
                \item \texttt{\textbackslash=/2}
                \item \texttt{==/2}
                \item \texttt{\textbackslash==/2}
            \end{itemize}
            \item \textbf{Control Predicates}
            \begin{itemize}
                \item \texttt{fail/0}
                \item \texttt{false/0}
                \item \texttt{true/0}
                \item \texttt{!/0}
                \item \texttt{,/2}
                \item \texttt{;/2}
                \item \texttt{|/2}
                \item \texttt{\textbackslash+/1}
            \end{itemize}
            \item \textbf{Database}
            \begin{itemize}
                \item \texttt{retract/1}
                \item \texttt{retractall/1}
                \item \texttt{asserta/1}
                \item \texttt{assertz/1}
                \item \texttt{assert/1}
            \end{itemize}
            \item \textbf{Declaring predicate properties}
            \begin{itemize}
                \item \texttt{dynamic/1}
            \end{itemize}
            \item \textbf{Delimited continuations}
            \begin{itemize}
                \item \texttt{reset/3}
                \item \texttt{shift/1}
            \end{itemize}
            \item \textbf{Examining the program}
            \begin{itemize}
                \item \texttt{clause/2}
            \end{itemize}
            \item \textbf{Forall}
            \begin{itemize}
                \item \texttt{forall/2}
            \end{itemize}
            \item \textbf{Loading Prolog source files}
            \begin{itemize}
                \item \texttt{consult/1}
                \item \texttt{initialization/1}
            \end{itemize}
            \item \textbf{Meta-Call Predicates}
            \begin{itemize}
                \item \texttt{call/1}
                \item \texttt{once/1}
                \item \texttt{ignore/1}
            \end{itemize}
            \item \textbf{Primitive character I/O}
            \begin{itemize}
                \item \texttt{nl/0}
            \end{itemize}
            \item \textbf{Term reading and writing}
            \begin{itemize}
                \item \texttt{write/1}
                \item \texttt{writeln/1}
                \item \texttt{read/1}
            \end{itemize}
            \item \textbf{Verify Type of a Term}
            \begin{itemize}
                \item \texttt{var/1}
                \item \texttt{nonvar/1}
                \item \texttt{atom/1}
                \item \texttt{compound/1}
            \end{itemize}
        \end{itemize}
    \end{multicols}


    \section{Bestaande Prolog implementaties}\label{sec:prolog-implementaties}

    Tijdens mijn onderzoek naar Prolog implementaties, kwam ik verschillende bestaande Prolog implementaties tegen die niet vermeld worden in~\cite{enwiki:1274527056}.
    Daarom zou ik durven stellen dat er geen gebrek is aan Prolog implementaties, maar dat er een gebrek is aan \emph{goede} Prolog implementaties.
    Vaak zijn deze implementaties niet meer dan een proof of concept.
    Ze waren enkel nuttig als inspiratie om specifieke problemen die zich tijdens de ontwikkeling van Ghent Prolog voordeden op te lossen.

    Voorbeelden van zulke implementaties zijn:

    \begin{itemize}
        \item \href{https://github.com/adamjstewart/prolog}{adamjstewart/prolog}, in OCaml
        \item \href{https://plzoo.andrej.com/language/miniprolog.html}{miniprolog}, in OCaml
        \item \href{https://github.com/benjamin-hodgson/Amateurlog}{benjamin-hodgson/Amateurlog}, in C\#
        \item \href{https://github.com/fmidue/prolog}{fmidue/prolog}, in Haskell
        \item \href{https://github.com/patirasam/Prolog-Interpreter}{patirasam/Prolog-Interpreter}, in Python
    \end{itemize}

    Van de implementaties die wel vermeld worden in~\cite{enwiki:1274527056}, zijn
    \href{https://www.swi-prolog.org/}{SWI-Prolog},
    \href{https://sicstus.sics.se/index.html}{SICStus Prolog} en
    \href{https://dobrev.com/}{Strawberry Prolog} de meest interessante.


    \section{Dankwoord}\label{sec:dankwoord}

    Bedankt, \href{https://www.swi-prolog.org/user/view_profile?user=c86f61d8-c201-11e6-84af-00163e986a2a}{LogicalCaptain}
    om steeds nuttige informatie en voorbeelden te geven bij SWI-Prolog documentatie die iets minder duidelijk is.
    Uw nota's waren verhelderend en zorgden voor een beter begrip van en voor de nuances van SWI-Prolog.

\end{document}
