Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prolog DCG set_prolog_flag double_quotes source code directive location matters; documentation?

I learned the hard way that with SWI-Prolog the location for the Prolog directive set_prolog_flag matters in a source code file.

The only documentation I found of value about loading source code files with directives was in Loading Prolog source files

A directive is an instruction to the compiler. Directives are used to set (predicate) properties (see section 4.15), set flags (see set_prolog_flag/2) and load files (this section). Directives are terms of the form :- <term>.

Is there documentation for SWI-Prolog that covers loading of source code that notes if a directive applies to the entire file or depends on the location in the source code file?

Or is it that all lines loaded from a source code file are just a simple playing of statements into the top-level and location always matters?

Supplement / TL;DR

Default

When using Definitive Clause Grammars (DCG) in Prolog it is known that DCG requires the input to be a list of character codes, e.g.

?- string_codes("abc123",Cs).
Cs = [97, 98, 99, 49, 50, 51].

and with the following DCG rule in a source code file and loaded into the top-level

digit(0) --> "0".

the DCG can be used with

?- string_codes("0",Cs),phrase(digit(D),Cs,R).
Cs = [48],
D = 0,
R = []

set_prolog_flag

Now to make it easier to use DCG instead of having to use string_codes the Prolog directive

:- set_prolog_flag(double_quotes, chars).

can be used in a source code file and with the following DCG rule in a source code file and loaded into the top-level

digit(0) --> "0".

the DCG can be used with

?- phrase(digit(D),"0",R).
D = 0,
R = [].

That left out something important

It turns out that the if set_prolog_flag appears before the DCG rule then skipping string_codes works, but if set_prolog_flag appears after the DCG rule then skipping string_codes fails.

:- set_prolog_flag(double_quotes, chars).
digit(0) --> "0".

?- phrase(digit(D),"0",R).
D = 0,
R = [].

vs

digit(0) --> "0".
:- set_prolog_flag(double_quotes, chars).

?- phrase(digit(D),"0",R).
false.

The reasoning that led me afoul

While I am aware that a lot of programming with Prolog can be done in just the top-level, I tend to rely on source code files and consult/1.
In writing lots of code I started to use modules. With modules I found out that the Prolog flags are independent for each module.

?- current_prolog_flag(double_quotes,V).
V = string.

?- current_prolog_flag(symbolic:double_quotes,V).
V = string.

?- set_prolog_flag(symbolic:double_quotes,chars).
true.

?- current_prolog_flag(double_quotes,V).
V = string.

?- current_prolog_flag(symbolic:double_quotes,V).
V = chars.

and that the default top-level module is user

?- current_prolog_flag(double_quotes,V).
V = string.

?- current_prolog_flag(user:double_quotes,V).
V = string.

?- set_prolog_flag(double_quotes,chars).
true.

?- current_prolog_flag(double_quotes,V).
V = chars.

?- current_prolog_flag(user:double_quotes,V).
V = chars.

?- set_prolog_flag(user:double_quotes,codes).
true.

?- current_prolog_flag(double_quotes,V).
V = codes.

?- current_prolog_flag(user:double_quotes,V).
V = codes.

which lulled me into the false belief that the Prolog directive set_prlog_flag applied to the entire module no matter where it was written.

What broke the mold

In writing lots of example code it was easier to keep all of the little examples in one file and associated with each little example was set_prolog_flag. For an identifier example it needed two little example DCG rules, one for digit and one for letters. The digit rules were above the letter rules and working, but the letter rules had the set_prolog_flag directive because I was working on them at the time. Remember I am thinking that the directive applies to the whole file at this point. Then in testing ident the DCG rules for letters were working but the DCG rules for digits were failing.

digit(0) --> "0", !.
digit(1) --> "1", !.
digit(2) --> "2", !.

:- set_prolog_flag(double_quotes, chars).

ident(Id) --> letter(C), identr(Cs), { name(Id, [C|Cs]) }.

identr([C|Cs]) --> letter(C), !, identr(Cs).
identr([C|Cs]) --> digit(C), !, identr(Cs).
identr([])     --> [].

letter(a) --> "a", !.
letter(b) --> "b", !.
letter(c) --> "c", !.

?- phrase(ident(Id),"ab12",R).
Id = ab,
R = ['1', '2'].

Root cause

So using listing/1

?- listing(digit).
digit(0, [48|B], A) :- !,
        A=B.
digit(1, [49|B], A) :- !,
        A=B.
digit(2, [50|B], A) :- !,
        A=B.


?- listing(ident).
ident(C, A, F) :-
        letter(D, A, B),
        identr(E, B, G),
        name(C, [D|E]),
        F=G.

?- listing(identr).
identr([A|D], B, F) :-
        letter(A, B, C), !,
        E=C,
        identr(D, E, F).
identr([A|D], B, F) :-
        digit(A, B, C), !,
        E=C,
        identr(D, E, F).
identr([], A, A).

?- listing(letter).
letter(a, [a|B], A) :- !,
        A=B.
letter(b, [b|B], A) :- !,
        A=B.
letter(c, [c|B], A) :- !,
        A=B.

the problem was apparent

digit(0, [48|B], A) :- !,
    A=B.

letter(a, [a|B], A) :- !,
        A=B.

that digit was converted to use character codes 48 and letter was converted to use characters a. That's when I asked myself if the location of set_prolog_flag in source mattered.

Confirming root cause

To test this I created a little source code file

digit_before(0) --> "0".

:- set_prolog_flag(double_quotes, chars).

digit_after(0) --> "0".

and in top-level

?- current_prolog_flag(double_quotes,V).
V = string.

?- current_prolog_flag(symbolic:double_quotes,V).
V = string.

?- consult("C:/Users/Eric/Documents/Projects/Calculus Project/test.pl").
true.

?- current_prolog_flag(double_quotes,V).
V = chars.

?- current_prolog_flag(symbolic:double_quotes,V).
V = string.

?- listing(digit_before).
digit_before(0, [48|A], A).

true.

?- listing(digit_after).
digit_after(0, ['0'|A], A).

true

which confirmed that the Prolog directive set_prolog_flag does not apply to an entire file. Notice that digit_before is converted to 48 and digit_after is converted to '0'.

Notes

Note: The directive set_prolog_flag(F,V) can also be used in the top-level and does not require the preceding :- .

Note: The example used :- set_prolog_flag(double_quotes, chars). but :- set_prolog_flag(double_quotes, codes). also works. Using chars value is preferred because it makes the values easier to read when debugging, etc.

like image 559
Guy Coder Avatar asked Jul 29 '17 13:07

Guy Coder


2 Answers

In SWI-Prolog, directives and clauses are processed in order. Prolog flags are complicated. The overall rule is that they are thread scoped, where child threads share the flags from their creator using copy-on-write semantics, which effectively means the same as when all flags would be copied except for performance and memory usage. But then, some flags are scoped to the source file in which they appear. This means that load_files/2 saves the state of the flag before the load and restores it afterwards. Some other flags are module scoped, which means the flag API is merely a proxy to changing a module attribute. Such flags are not thread-specific because modules are global. Also note that some flags affect reading (e.g., double_quotes), while others affect the compiler (optimise) and most affect runtime behaviour.

Ideally, the documentation with current_prolog_flag/2 should document these aspects. Not sure this documentation is accurate. For double_quotes it says maintained for each module.

like image 188
Jan Wielemaker Avatar answered Oct 17 '22 20:10

Jan Wielemaker


I may say that you can make for sure that set_prolog_flag(double_quotes, chars) directive has the desired behavior (applicability to an entire file).

This can be done by using initialization/2. directive with the option after_load, or by using initialization/1.

digit_before(0) --> "0".

:- initialization( set_prolog_flag(double_quotes, chars),  after_load ).

digit_after(0) --> "0".

SWI-Prolog initializаtion/2 directive

SWI-Prolog initializаtion/1 directive

Regarding the problem how to suggest your ideas to the SWI-Prolog community I hope the (initial) solution is the presence of the second answer.

Useful links:

Research papers by Ulrich Neumerkel and Fred Mesnard

Home Page of Markus Triska

Contains a large number of diverse materials dedicated to the programming language Prolog.

like image 43
Anton Danilov Avatar answered Oct 17 '22 21:10

Anton Danilov