Stripped out modules, unit test no longer work

I am almost out of ideas and horridly frustrated at my inability to once again understand the docs. I’ve read the text regarding the user modules, load_test_files, and yet none of it makes any sense.

How does one write an application in traditional (no modules) and then unit test that applications predicates using begin_test ? I have tried ensure_loaded and all other things, even this page didn’t provide an answer;
https://www.swi-prolog.org/pldoc/man?predicate=load_test_files/1

If I prefix the predicate under test with user: then it works but I really don’t want to have to do that. I tried adding :-module(user) just after the begin_test but that had no effect.

My project structure is:

root/
    load.pl
    src/
       *.pl and *.plt files

In my load file I have this set as the initialisation predicate but Eclipse PDT seems not to run it so I have to manually run setup/0 each time,

:- initialization(setup).
setup :-
    set_prolog_flag(encoding, utf8),
    expand_file_name('src/*.pl', Sources),
    load_files(Sources),
    load_test_files([]),
    module(user),
    % This makes PDT in Eclipse better!
    working_directory(_, '/Users/sean/Documents/code/prolog/f2').

A typical testing scenario:

:- begin_tests(buffer_location).
test(line_update_resets_col_bumps_line,
    [true(Out == (1001, 101, 0))]) :-
    movepos(0'\n, (1000, 100, 200), Out).
:- end_tests(buffer_location).

This FAILS unless I put user:movepos as that’s where the predicate lives now rather than the module it used to be in…

plunit_buffer_location:'unit body'/2: Unknown procedure: plunit_buffer_location:movepos/3

I’ve tried adding :-ensure_loaded(tokeniser),

Once again I find myself fighting the environment instead of doing something within it.

:frowning:

Maybe I am too dumb after all, I mean if you can’t figure out something this simple, writing tests, then really, ought you to be in the game at all ?

IMHO I do not consider no modules as traditional.

About the only time I use code without modules is to do some quick predicates in the top level or to quickly answer a posted question with some code. My default way of writing any Prolog code I intend to keep around is to use modules. As a matter of fact I rely on modules so much that I typically cut and paste code like this when opening a new *.pl file.

:- module(module_name,
    [
        prediate_1/3,
        prediate_2/5
    ]).

% -----------------------------------------------------------------------------

% DELETE THIS SECTION BEFORE POSTING

:- if(current_prolog_flag(windows,true)).
:- working_directory(_,'C:/Users/Me/Documents/Projects/SWI-Prolog/Project').
:- elif(current_prolog_flag(unix,true)).
:- working_directory(_,'/mnt/c/Users/Me/Documents/Projects/SWI-Prolog/Project').
:- endif.

% [module_name].

% ----------------------------------------------------------------------------

:- multifile
    other_module:other_prediacate/3.

% ----------------------------------------------------------------------------

and like this for the corresponding *.plt file

% Note: No module declaration for plt files.

% -----------------------------------------------------------------------------

% DELETE THIS SECTION BEFORE POSTING

:- if(current_prolog_flag(windows,true)).
:- working_directory(_,'C:/Users/Me/Documents/Projects/SWI-Prolog/Project').
:- elif(current_prolog_flag(unix,true)).
:- working_directory(_,'/mnt/c/Users/Me/Documents/Projects/SWI-Prolog/Project').
:- endif.

% [module_name].

% [trace]  ?- run_tests(my_module:my_test).

% ----------------------------------------------------------------------------

:- begin_tests(my_module).

:- use_module(other_module_1).
:- use_module(other_module_2).

% count tests

% Number of unique something.
test(count_of_something) :-
    % gtrace,
    number_of_something(10).

:- end_tests(my_module).

I did a lot of changes in the above code so as not to reveal real names and such so don’t expect the code to compile if copied.


Don’t even think that!!! :grinning:

I get stuck on problems so often that when a day goes by without a problem I wonder if I did anything productive.

As I have noted many times here, I spent many many months grappling with DCGs before I finally felt comfortable with them. Still don’t have them down enough that I could write a Wiki I consider complete but do know them enough that I don’t hesitate to use them for any problem needing Prolog list, generators or parsers.


One thing I have learned about using Prolog with modules is that if I think in hierarchies of modules like object oriented languages it leads to nothing but trouble. The Prolog module system is very flat. What helped me understand this was using Cystoscape to graph the module dependencies: See: Wiki Discussion: Generating Cytoscape.js graphs with SWI-Prolog - #4 by EricGT.

The posted graph is not what gave me the ah-ha moment but after hand adjusting the vertexes it lead to the ah-ha moment.

Ironically Eric, that is exactly how my project was and everything was fine, good job I created a temporary git branch for it.

I think part of the problem is, as you rightly point out, approaching the module system as an OO programmer; my day job involves python, java and elixir. all have their own interpretations of what a ‘module’ is.

I guess this will bring me back to my fundamental problem that led me to thinking that removing modules was the way forward…

…how to have a large set of DCG rules split over multiple files. I have 84 reserved words in my language, each requires its own set of rules for checking the various forms that can be expressed and I wanted to have a separate file for each one, and the tests in a separate file too although that’s not connected with my main problem.

I made a post about having to use :- multifile and then declaring predicates in their own modules but not using the module name as the exported entry point just to make it work, the full description of my quandary is here: sean charles :: Consulting -> SWI Prolog, multifile predicates in modules.

However, the response to my post was that it was ‘hacky’ and unnecessary. But no explanation given as to alternatives.

Surely there is a more obvious way… part of the issue is that I have never written a large (for me) prolog project before and I don’t know what’s the normal practice for managing lots of files.

Back to the drawing board… thanks for your time @EricGT

For me it is

how to have a large set of DCG rules split over multiple files with more than two hierarchy layers.

I don’t know an answer or even have a solution I like or would even share.

When I ran into the problem a few months ago I finally fell back to using just one or two levels of modules; can’t remember which off the top of my head, but know that when I tried three layers of modules I could see no way to make it work; I even thought about removing the module system from SWI-Prolog and growing a new one but that would make the code unusable in the wild.

I do remember that I learned a lot about multifile/1 and meta_predicate/1 in trying to understand and find a solution but using those felt more like a crutch than a simple cookie cutter solution.


I would not be so sure. Having been out on this limb for a while the only one I suspect who has an idea is Wouter Beek. Note that we both use DCGs with open list while many others used closed list.


Take a look at the other GitHub repositories that have large SWI-Prolog projects for ideas. TeamSPoon is one of my favorites.


EDIT

One thing worth looking at is Dynamic Modules. I did not try this as I was able to move along again just using fewer hierarchy levels but may circle back to this in the future when I give my code more things to solve.

While I still can’t get my head wrapped around TerminusDB that was also on my list of things to consider for solving this problem.

Wow, you have solved all my problems Eric.

That obsoletes my project and nine years of mental torture for not finishing anything.

What a great day, the chains are released.

Thank you !

I quit.

That seems rather odd. Maybe this has more to do with how Eclipse PDT works than with how SWI-Prolog works. I’d first try this in a terminal. Just run this (adjusting the name of the load file). Before
doing so, remove module(user) and the working_directory/2 calls. That stuff should not be needed.

swipl load.pl

Explicit Module: calls should hardly ever be required. For testing there is one exception. If you use separate test files you can only test the interface of the module you want to test. If you want to test internal predicates either use embedded tests or use Module: prefixed calls.

Otherwise it is a bit hard to comment. You do give the directory and file structure, but not the module headers of these files. To Prolog, file and directory names are irrelevant. Directories do not create some sort of module hierarchy. Modules live in a flat namespace. f you want some sort of hierarchy in that you have to use your own naming conventions.

Here is what I am currently using to write modules and test the interfaces in separate files:

:- begin_tests(top_level_sexp).
:- use_module(myapp(helpers)).
:...tests...
:- end_tests(top_level_sexp)

:- begin_tests(maps).
:- use_module(myapp(helpers)).
:- export(ast:map/3).
:- import(ast:map/3).
:...tests..
:- end_tests(maps).

etc.

%% and the module headers

:- module(ast,
        [
            ast/2,
            ast/3,
            ast_runner/3,
            % DCG exports            
            all_single_or_sexp//1,
            ast_syntax//1,
            ast_syntax//2,
            d//1,
            d//2,
            expect//4,
            expect_cp//2,
            expect_op//2,
            function_call//1,
            get_pos//1,
            list//1,
            map//1,
            reserved_term//1,
            single_or_sexp//1,
            single_token//1,
            tk_single_sexp//2
        ]).

I added the module(user) and working_directory/2 calls FOR PDT in Eclipse and I also had to change the eclipse.ini file as it wasn’t detecting the locale and giving me UTF-8 issues.

I still don’t understand modules then.

This whole pain-packet began when I mentioned how I had found -a way- of being able to split my ast code across multiple modules but I somehow felt that my solution was regarded as unclean, impure, dirty and possibly illegal in some states.

In my various split out ast “modules” I have…

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

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

:- module(emit, [ast:inst//2]).  %% file: emit.pl
:- multifile ast:inst//2.

%% and the offensive to humanity calling code:
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)])
    },
    RealFn:inst(Term, Out).

%% all because I just couldn't figure any other way

I think I am going to go back to what I had, it worked, the unit tests worked ad to be honest I don’t think I care anymore other than it works and will eventually compile out into a standalone application.

Thanks everybody for your time and patience dealing with an Impostor Prolog user.

Apparently there should be a stricter check in the module header. The export list is not supposed to hold qualified terms. Using module_property/2, we see defun doesn’ t export anything. Well, actually it creates a module ast that exports inst//2 …

The module system is supposed to put predicates inside a module, making some publicly visible and hiding the others. It is not an object system. It is not that different from the C notion of common functions (visible outside the file) and static functions (invisible outside). And (thus), typically the definition of an entire predicate lives inside one module.

Multifile is relatively complicated concept that allows splitting the definition of a single predicate over multiple files. It can be used together with modules, but that is not the most obvious way to do things. If you just want to split the inst//2 rules over files I’d consider using include/1 to include the files in the ast modules. The include/1 directive just inserts the source code at that point.

Still, If you want to go the module route, I’d have a module ast as in

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

Any file that needs inst//2 imports (use_module/1,2) the module ast. Any file that wishes to contribute to the implementation does so as below. Note that the body of the ast:inst//2 DCGs is executed in the context of the defun module.

:- module(defun, []).

:- multifile ast:inst//2.

ast:inst(X,Y) --> ...

There is a quite good module tutorial here http://chiselapp.com/user/ttmrichter/repository/gng/doc/trunk/output/tutorials/swiplmodtut.html

1 Like

HA ha ! :slight_smile: Thank you Jan, I will read that page and still made a hash of it (no pun intended).
I think the fundamental issue is I have to STOP thinking like an OO person, as I’ve said I daily use python, Erlang and elixir and they all have there notions on ‘modules’ and how to work them.

Could you explain. “it -creates- a module that exports inst/2” ?, that was def. not what I thought I was doing!!!

Thanks for your time, again.

Sometimes the different paradigm and all the logic-centric terminology goes over my head, I am not a CS grad, didn’t go to University, everything I’ve learned has been through my own efforts and self-learning sometimes leaves gaps as you don’t follow a prescribed trail like a syllabus. I just learn what I need and press on but that is not always the best way. It’s a great roller coaster ride but the occasional derailment makes it scary!

I WILL re-read that page but this time of course I am armed with much more extra information to place against it; my failures. Failures are only valuable as learning material. I should write my autobiography someday…

Thanks again.
Sean

If you write :- module(x, [m:p/1]). it should probably raise a type error. What happens though is that it tries to export m:p/1 from the current context x. That calls export(x:m:p/1). As for a nested qualification the inner one counts, this exports p/1 from m. If m doesn’t exist yet it will be created as a side effect. As m:p/1 is in your case a multifile predicate that is considered defined even if there are no clauses this raises no further warnings.

Modules are supposed to export their own predicates, not the predicates of other modules :slight_smile:

In a layered architecture, it does sometimes happen to me that I want to export a predicate defined at a lower level – essentially, forwarding the call, without unnecessarily needing to expose the lower level module to a higher level one.

One approach is to create a “forwarding predicates” which simply calls the lower level one – but, i then often wonder if this indirect doesn’t hurt performance and i should “term expand” it away – or support some kind of direct reexport.

Dan

Modules may export predicates they import. There is no overhead involved in that. A link clause gives a little overhead, though not much anymore in the development version.