Why is prolog_load_context with user:file_search_path done this way?

In this code example in the SWI-Prolog documentation (ref)

:- prolog_load_context(directory, Dir),
   asserta(user:file_search_path(myapp, Dir)).

was thinking that it should instead work as

user:file_search_path(myapp, Dir) :-
   prolog_load_context(directory, Dir).

In the first it is a directive (:-) that gets the directory from prolog_load_context/2 and then uses asserta/1 to add the hook predicate user:file_search_path/2 with the alias myapp.

However since user:file_search_path/2 is a hook that is typically called from absolute_file_name/2 then the second way should work. However in trying that in code if failed.

So looking into the predicates see in the documentation for prolog_load_context/2

Obtain context information during compilation.

which leads me to think that this predicate will only work correctly during the compiling. Now in Prolog when I think of what happens during compilation I naturally think of conditional compilation and program transformation so was expecting one of those in the call path when looking at the implementation of prolog_load_context/2 but in looking at the source code find.

(ref)

prolog_load_context(directory, D) :-
    input_file(F),
    file_directory_name(F, D).

(ref)

input_file(File) :-
    (   system:'$load_input'(_, Stream)
    ->  stream_property(Stream, file_name(File))
    ),
    !.
input_file(File) :-
    source_location(File, _).

with $load_input being asserted and volatile

(ref)

:- thread_local
    '$load_input'/2.
:- volatile
    '$load_input'/2.

'$open_source'(stream(Id, In, Opts), In,
               restore(In, StreamState, Id, Ref, Opts), Parents, _Options) :-
    !,
    '$context_type'(Parents, ContextType),
    '$push_input_context'(ContextType),
    '$prepare_load_stream'(In, Id, StreamState),
    asserta('$load_input'(stream(Id), In), Ref).
'$open_source'(Path, In, close(In, Path, Ref), Parents, Options) :-
    '$context_type'(Parents, ContextType),
    '$push_input_context'(ContextType),
    '$open_source'(Path, In, Options),
    '$set_encoding'(In, Options),
    asserta('$load_input'(Path, In), Ref).

with the asserted term being erased when closing the source

(ref)

'$close_source'(close(In, _Id, Ref), Message) :-
    erase(Ref),
    call_cleanup(
        close(In),
        '$pop_input_context'),
    '$close_message'(Message).
'$close_source'(restore(In, StreamState, _Id, Ref, Opts), Message) :-
    erase(Ref),
    call_cleanup(
        '$restore_load_stream'(In, StreamState, Opts),
        '$pop_input_context'),
    '$close_message'(Message).

thus the data for prolog_load_context/2 as noted in the documentation is only available when the code is being loaded and thus called with a Prolog directive.


So if Jan W. can just confirm that this line of reasoning is correct it would be nice to know. Then I have to add another concept to my long list of how SWI-Prolog really works.

This is how it works. prolog_load_context/2 comes from Quintus and is mostly needed for term/goal expansion, in particular to find locations, target module, etc.

There is another way to refer to the directory of a source file:

dummy.

user:file_search_path(myapp, Dir) :-
    source_file(dummy, File),
    file_directory_name(File, Dir).

Usually you have other predicates defined in the file and you can pick any of them instead of dummy.

1 Like