SWI Prolog, multifile predicates in modules.

λ April 5, 2021
Tags: prolog, swi

TL;DR – use :-multifile mod:pred in the main file and then in every broken out module use

:-module(foo, [mod:pred])

and actually declare the predicate as mod:pred even though you are in a different module namespace and it works! Read on for the full details, it’s obviously simple once you see it. It caused me some head scratching for a while but some diving back to 2010 found a solution.

I am working on a transpiler project in SWI Prolog and the file containing the code for the AST construction phase is getting large, and so I decided to break it down into chunks. This posed some issues regarding finding the code and also how to use the multifile/1 predicate to make the same predicate be used across different files whilst maintaining a clean separation of each modules code.

The Problem

I have a file, ast.pl, which contains all of the DCG analysis for my transpiler language. Currently there are somewhere between eighty-four and eighty-seven reserved words in the language, that sounds like a lot but compared to say the standard Common Lisp library, it isn’t.

Anyway, as I have made progress I have wanted to break out the main code into smaller more manageable source files so that I can develop and test them in isolation.

There is a way in Prolog to tell the system that a predicate can be defined across multiple files but I wasn’t sure how to do this across disparate modules as well. That is, to have different named modules (named after the instruction they contain the parsing rules for) but somehow be able to define an additional ‘common’ predicate.

The thread I found is here:

http://swi-prolog.996271.n3.nabble.com/About-quot-multifile-quot-td6839.html

The Solution

There is a predicate called multifile that allows you to tell the Prolog system that a predicate can be expected to be declared in more than one file and that when a ‘new’ declaration is read in from a source file, that the old one should not be junked but instead there would now be one or more in existence, which is bread-and-butter to Prolog i.e. you are just growing the database.

The subtle part of the solution was learned thanks to a posting by P. Moura back in 2010 which I found, read, understood and then applied to my code to allow me to be able to use modules to reduce the size of the AST file and also to allow separate files to be used for each and every instruction in my transpiled language.

Paulos suggestion was subsequently expanded on and confirmed by -the- Richard A. O’Keefe (I have his book, The Craft of Prolog), and it didn’t take long to get a working solution for my project.

This is great as I can either put the associated tests in the same place or even in the same file if I wanted, although I tend to keep them into the .plt file instead. For example a file called defun.pl would have all of its tests in the file defun.plt and the SWI environment manages them for me when I issue the make/0 and run_tests/0 command at the session REPL.

Show me the code!

In a nutshell, the main file, ast.pl has this at the top, it states that the predicate ast:inst//2 is allowed to be defined in multiple files, the //2 means it is a DCG rule, it could have been declared as /4 but the double-slash makes it more obvious…

%
%         DCG Syntax Handlers
%
:- multifile ast:inst//2.
:- use_module(myapp(assign)).
:- use_module(myapp(defun)).
:- use_module(myapp(defvar)).
:- use_module(myapp(emit)).

There are many more, and many more needed :| Perseverance is key at times like these. Now comes the interesting bit, defining the –same– predicate in different modules but ensuring that the naming is consistent, this has to be the case because the calling code is like this:

built_in_func(Out) -->
    [Term], {
        Term =.. [Fn, _Pos],
        reserved_word(Fn),
        bifmap(Fn, RealFn),
        debug(ast, "built_in_func: calling as: ~w", [inst(Term,Out)])
    },
    inst(Term, Out).  %% <--- calling 'ast:inst' in reality!

Now let’s look in the first two modules, assign and defun to see what you have to do, it’s very simple and obvious, afterwards…

:- module(assign, [ast:inst//2]).
:- multifile ast:inst//2.

ast:inst(assign(Pos), assign(Pos, Var, RHS)) -->
  :
  : ..code..


:- module(defun, [ast:inst//2]).
:- multifile ast:inst//2.

ast:inst(defun(Pos), defun(Pos, pub(Name), Args, Body)) -->
    defun_(defun_function_name, Pos, Name, Args, Body).

So there you go. I can now break it down, and take it to the bridge. Note that in each of the two examples, the module declaration states that it exports the explicitly named predicate ast:inst//2, normally one puts just the name of the predicate, that’s the secret sauce to making it work.

I hope that helps anybody else struggling with multifile predicates across different modules in SWI Prolog.