Package edcg needs module predicates to work, is there another abstraction level possible?

:- use_module(library(edcg)).

% akku and predicatename configuration / definition via module predicates :
edcg:pred_info(test001, 0, [akku]).
edcg:acc_info(akku, T, Out, In, (Out=[T|In])).

test001 -->> [a]:akku.

/*
(ins)?- test001(A,B).
A = [a|B].
*/

Lets say I want to use the edcg module in different modules with different predicate sets. But obviously all that modules share the same predicate set in edcg but I want to avoid conflicts.

Is there any possibility to define the predicates pred_info and acc_info in another module m (dynamic or static) to have a better separation, and to call test001 anyway without erroring?

The Package edcg doesn’t seem to make use of module sensitivity via meta_predicate. It would be cool if there were another mechanism to say for one goal if I call (indirectly) predicates of module edcg it has also to look up at module m.

Best regards,
Frank

2 Likes

Seems that acc_info/5 could be moved in a service module… I worked out the example based on expr_code/5, moving in info.pl at least such declaration… guess the pred_info can be moved as well, but then it should be exported from info.pl.

file info.pl

:- module(info,[]).
:- use_module(library(edcg)).

% edcg:pred_info(expr_code_ssu, 1, [size,code]).

edcg:acc_info(code, T, Out, In, Out=[T|In]).
edcg:acc_info(size, T, In, Out, Out is In+T).

file expr_code_edcg.pl:

:- module(expr_code_edcg,
          [expr_code_edcg/5]).

:- use_module(library(edcg)).
:- use_module(info).

edcg:pred_info(expr_code_edcg, 1, [size,code]).

expr_code_edcg(A+B) -->>
    expr_code_edcg(A),
    expr_code_edcg(B),
    [plus]:code,
    [1]:size.
expr_code_edcg(I) -->>
    {atomic(I)},
    [push(I)]:code,
    [1]:size.

file expr_code_ssu.pl

:- module(expr_code_ssu,
          [expr_code_ssu/5]).

:- use_module(library(edcg)).
:- use_module(info).

edcg:pred_info(expr_code_ssu, 1, [size,code]).

expr_code_ssu(A+B) ==>>
    expr_code_ssu(A),
    expr_code_ssu(B),
    [plus]:code,
    [1]:size.
expr_code_ssu(I) ==>>
    {atomic(I)}, % TODO: this should be a guard
    [push(I)]:code,
    [1]:size.

running:

?- [expr_code_edcg,expr_code_ssu].
true.

?- E=(a+3+b),expr_code_edcg(E, 0,S1, C1,[]), expr_code_ssu(E, 0,S2, C2,[]).
E = a+3+b,
S1 = S2, S2 = 5,
C1 = C2, C2 = [push(a), push(3), plus, push(b), plus] ;
false.

This is not what I mean. Your modules
expr_code_edcg and expr_code_ssu see both edcg:acc_info predicates which are defined in module info. We have no kindof namespace nesting here. When you load your file info.pl in whichever module then the defined edcg:acc_info predicates are accesible from all other modules as edcg:acc_info.

Both modules need both accumulators so it is no problem at all for your concrete example.

(ins)?- module(testmodule).
Warning: testmodule is not a current module (created)
true.
(ins)testmodule:  ?- consult('info.pl').
true.
(ins)testmodule:  ?- edcg:listing(acc_info).
:- multifile acc_info/5.
acc_info(code, T, Out, In, Out=[T|In]).
acc_info(size, T, In, Out, Out is In+T).

:- multifile acc_info/7.

(ins)testmodule:  ?- module(user).
true.

(ins)?- edcg:listing(acc_info).
:- multifile acc_info/5.

acc_info(code, T, Out, In, Out=[T|In]).
acc_info(size, T, In, Out, Out is In+T).

:- multifile acc_info/7.

What I mean is maybe better explained at another example.

lets say I don’t create the acc_info not in edcg like this:

edcg:acc_info(akku, T, Out, In, (Out=[T|In])).

but I create it in a dynamic module m1 and m2 :

% 2 different definitions:
m1:acc_info(akku, T, Out, In, (Out=[T|In])).
m2:acc_info(akku, T, In, Out, (Out=[T|In])).

I use the same name ‘akku’ intentional to make the problem more visible.

Then I would have 2 extra modules with code, lets name it use_m1 and use_m2. How can the both other modules use_m1 and use_m2 make separate use of m1:acc_info and m2:acc_info together with edcg?

Kind regards,
Frank

Sorry, I don’t know…

The EDCG code is over 30 years old, so it’s amazing that it works at all. :wink:
[I did a bit of cleanup on it, and added support for ssu (=>), but that’s all.

If anyone wants to work on it, I suggest they also look at how logtalk handles EDCGs: Multi-pass compilation? (for term expansion) - #2 by pmoura

PRs are welcome.

And I wondered why the documentation has such a nice typewriter font :slight_smile:

I mean I am not really sure if I will use edcg for my project. I just wanted to know what are the benefits compared with dcg. Maybe I do not see the whole picture yet. But instead of using different accumulators I could use an assoc and modify it. I can imagine edcg is maybe faster, even if compiled. But dcg is still there and needs no package installation.

But in the other hand - I dug already in logtalk, but I was not aware that there already exists an improved variation of edcg.

But a “module shadowing” or “copy on write” mechanism for modules or something like that, from which the original edcg package would benefit from, could be another topic worth discussing. Because it would improve the handling of the edcg package without changing it.

Ok, thanks for you answers so far.

Kind regards.

If you have lots of accumulators and don’t want to have very long predicate heads with tens of arguments this comes in handy. Also if the predicate does not make use of the accumulator, then the code for the accumulator does not have to appear in the human readable version of the predicate; the rewritten predicate passed to the compiler will have all of the boilerplate.

When I used EDCGs I used them mostly like other programming language getter and setters. But if you create custom rules you can do a lot more, you might even be able to convert other programming languages into Prolog and compile and run them, I have not tired that yet but would not be surprised if it could be done.

However others have used SWI-Prolog records to a similar effect.

HTH

  • “Accumulators” can have arbitrary implementation, whereas DCGs only have difference lists (e.g., I have a symbol table accumulator that uses library(rbtrees), and can be used in both lookup-symbol and add-symbol contexts).
  • Multiple accumulators - DCGs have only a single accumulator.
  • Can be used to pass around “global” data such as options (a kind-of accumulator that is never modified).

I better get acquainted with EDCG – all these sound wonderful – never was able to get my head around it though …

Any simple step by step tutorial available for such features?

Dan

I haven’t looked at the details. Can’t be too hard to expect the accumulators in the current module rather than the edcg module, no? Than we can normally import/export the accumulators and have all the normal hiding.

No.

To put that question into a perspective from the way I view them is if one knows the relationship between constraint libraries and CHR then that is similar to EDCGs. But with EDCGs I saw no clear separation between the parts. (Which is why I find the topic question interesting) So trying to give a (comprehensive) tutorial for EDCGs would be hard. As used for the Aquarius compiler it makes sense but once you learn how to make new rules then you realize how much more you can do and I personally can’t even list all of the possibilities.

The best at present is to read the papers noted in documentation and just work with it. After a few days you might be able to teach those of us using it a new trick or two. :slightly_smiling_face:

I think that a few well explained introductory examples could surely help – to get started with own explorations.

That’s how it worked for me with DCG which eventually, I also finally understood how to use for monad like calls.

Dan

There are some examples in the t directory. The t directory name is a hold over from when unit test were done using using tap. The test have been updated to unit test.

It was not until I used listing on the rewritten code and then compared both that it started to make sense how the EDCG rules were being applied. I even had to modify the rewrite code with print statements to understand the interplay of the rules. It started to make sense after a few days.


EDIT

In looking at puzzle.pl that looks a lot like the planner code. (ref)

I have not tried converting the puzzle into planner rules and checking but it looks trivial.

I’ve tried, but my solution is not backward compatible, so I wait for better solutions…
file puzzle.pl: just removed the edcg: qualifications of acc_info,pass_info,pred_info

% -*- mode: Prolog -*-

% A simple puzzle solver, showing the use of EDCG notation.
% Solves https://en.wikipedia.org/wiki/Fox,_goose_and_bag_of_beans_puzzle

%:- use_module('../prolog/edcg.pl').
:- use_module(library(edcg)).
:- use_module(library(apply)).
:- use_module(library(lists)).

:- multifile acc_info/5,pass_info/1,pred_info/3.

%! print_solutions is semidet.
% Find all the solutions and print them out.
print_solutions :-
    setof(Moves, puzzle_moves(Moves), Solutions),
    print_term(Solutions, []).

% The "move" accumulator is a standard DCG-like accumulator
/*edcg:*/acc_info(move, T, Out, In, Out=[T|In]).

% The "state" accumulator keeps a list of seen states in reverse order
% and when a new state is added, checks that the state hasn't already
% been seen.
/*edcg:*/acc_info(state, NewState, States0, States, add_state(NewState, States0, States)).

add_state(NewState, SeenStates, [NewState|SeenStates]) :-
    \+ memberchk(NewState, SeenStates).

% The "final_state" accumulator is an example of passing a "context".
/*edcg:*/pass_info(final_state).

% The puzzle//2 predicate takes 2 parameters (Side, State) and
% implicitly passes around 3 accumulators, for a total of 5 extra
% parameters (1 for final_state, 2 for move, 2 for state).
/*edcg:*/pred_info(puzzle, 2, [final_state, move, state]).

%! puzzle_moves(-Moves) is nondet.
% Find one solution, putting the moves into the Moves parameter.
puzzle_moves(Moves) :-
    puzzle(hither,  % initial side
           state{farmer:hither, fox:hither, goose:hither, beans:hither},  % initial state
           state{farmer:yon,    fox:yon,    goose:yon,    beans:yon},     % final state
           Moves, [],  % move accumulator
           [], _       % seen_states accumulator
          ).

%! puzzle(+Side, +State) is nondet.
%         accumulators: [final_state, move, state]
% Given the current Side (hither, yon) and a State, compute a new
% move; terminate if already at the final state.
puzzle(_Side, State) -->>
    State/final_state,  % FinalState/final_state, FinalState = State
    !.
puzzle(Side, State) -->>
    % The "{...}" isn't needed because there are no edcg:pred_info/3
    % clauses for these predicates, but it's good documentation.
    { other_side(Side, OtherSide),
      cross_in_boat(State, Passengers),
      new_state(Passengers, OtherSide, State, State2),
      valid_state(State2),
      select(farmer, Passengers, PassengersWithoutFarmer)
    },
    [ State2 ]:state,
    [ PassengersWithoutFarmer:Side->OtherSide:State2 ]:move,
    puzzle(OtherSide, State2).

%! cross_in_boat(+State, -Passengers:list) is nondet.
% Select a passenger (or none) from the State; Passengers is either
% the farmer or farmer plus a single passenger.
cross_in_boat(_State, [farmer]).
cross_in_boat(State, [farmer, Passenger]) :-
    get_dict(Passenger, State, State.farmer), % Select passenger from same location as farmer
    Passenger \= farmer.

:- det(new_state/4).
%! new_state(+Passengers, +OtherSide, +State, -State2) is det.
% Generate a new State2 with the Passengers (which will also include
% the farmer) on the OtherSide.
new_state(Passengers, OtherSide, State, State2) :-
    foldl(state_move_passenger(OtherSide), Passengers, State, State2).

:- det(state_move_passenger/4).
%! state_move_passenger(+OtherSide, +Passenger, +State, -State2)  is det.
% Create a new state with the Passenger on the OtherSide.
state_move_passenger(OtherSide, Passenger, State, State2) :-
    put_dict(Passenger, State, OtherSide, State2).

%! valid_state(+State) is semidet.
% Test whether a State is valid (fox can't eat goose, goose can't eat beans)
valid_state(state{farmer:FarmerSide, fox:FoxSide, goose:GooseSide, beans:BeansSide}) :-
    (FoxSide \= GooseSide; FoxSide = FarmerSide),
    (GooseSide \= BeansSide; GooseSide = FarmerSide).

%! other_side(+Side, -OtherSide) is det.
% Enumerate the hither/yon, yon/hither values for switching sides.
other_side(hither, yon).
other_side(yon,    hither).

modifield library(edcg): removed the multifile declaration, (check for existence and) call M:pred_info, M:call_info, M:pass_info where M is recovered from prolog_load_context/2.


:- module( edcg, [
    op(1200, xfx, '-->>'),   % Similar to '-->'
    op(1200, xfx, '==>>'),   % Similar to '-->'
    op( 990,  fx, '?'),      % For guards with '==>>'
    edcg_import_sentinel/0
]).

% If running a version of SWI-Prolog older than 8.3.19, define the
% '=>' operator to prevent syntax errors in this module.  The '==>>'
% operator is still defined in the module export, even though it'll
% generate a runtime error if it's used.
:- if(\+ current_op(_, _, '=>')).
:- op(1200, xfx, '=>').
:- endif.

:- use_module(library(debug), [debug/3]).
:- use_module(library(lists), [member/2]).

% These predicates define extra arguments and are defined in the
% modules that use the edcg module.
/*
:- multifile
    acc_info/5,
    acc_info/7,
    pred_info/3,
    pass_info/1,
    pass_info/2.
*/

% True if the module being read has opted-in to EDCG macro expansion.
wants_edcg_expansion :-
    client_module(Module),
    predicate_property(Module:edcg_import_sentinel, imported_from(edcg)).

client_module(Module) :-
    prolog_load_context(module, Module),
    Module \== edcg.  % don't expand macros in our own library

% dummy predicate exported to detect which modules want EDCG expansion
edcg_import_sentinel.


% term_expansion/4 is used to work around SWI-Prolog's attempts to
% match variable names when doing a listing (or interactive trace) and
% getting confused; this sometimes results in a strange error message
% for an unknown extended_pos(Pos,N).

% Returning a variable for _Layout2 means "I don't know".
% See https://swi-prolog.discourse.group/t/strange-warning-message-from-compile-or-listing/3774

% TODO: support ((H,PB-->>B) [same as regular DCG]
user:term_expansion((H-->>B), _Layout1, Expansion, _Layout2) :-
    edcg_term_expansion((H-->>B), Expansion).
user:term_expansion((H,PB==>>B), _Layout1, Expansion, _Layout2) :-
    edcg_term_expansion((H,PB==>>B), Expansion).
user:term_expansion((H==>>B), _Layout1, Expansion, _Layout2) :-
    edcg_term_expansion((H==>>B), Expansion).


% Perform EDCG macro expansion
% TODO: support ((H,PB-->>B) [same as regular DCG]
edcg_term_expansion((H-->>B), (TH:-TB)) :-
    term_expansion_(H, B, TH, TB, NewAcc),
    '_finish_acc'(NewAcc),
    !.
edcg_term_expansion((H,PB==>>B), (TH,Guards=>TB2)) :-
    '_guard_expansion_'(PB, Guards),
    term_expansion_(H, B, TH, TB, NewAcc),
    '_finish_acc_ssu'(NewAcc, TB, TB2),
    !.
edcg_term_expansion((H==>>B), (TH=>TB2)) :-
    term_expansion_(H, B, TH, TB, NewAcc),
    '_finish_acc_ssu'(NewAcc, TB, TB2),
    !.

% TODO: Do we want to expand the guards?
%       For now, just verify that they all start with '?'
'_guard_expansion_'((?G0,G2), (G, GE2)) :- !,
    '_guard_expansion_curly_'(G0, G),
    '_guard_expansion_'(G2, GE2).
'_guard_expansion_'(?G0, G) :- !,
    '_guard_expansion_curly_'(G0, G).
'_guard_expansion_'(G, _) :-
    throw(error(type_error(guard,G),_)).

'_guard_expansion_curly_'({G}, G) :- !.
'_guard_expansion_curly_'(G, G).


term_expansion_(H, B, TH, TB, NewAcc) :-
    wants_edcg_expansion,
    functor(H, Na, Ar),
    '_has_hidden'(H, HList),
    debug(edcg,'Expanding ~w',[H]),
    '_new_goal'(H, HList, HArity, TH),
    '_create_acc_pass'(HList, HArity, TH, Acc, Pass),
    '_expand_goal'(B, TB, Na/Ar, HList, Acc, NewAcc, Pass).

% Expand a goal:
'_expand_goal'((G1,G2), (TG1,TG2), NaAr, HList, Acc, NewAcc, Pass) :-
    '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass),
    '_expand_goal'(G2, TG2, NaAr, HList, MidAcc, NewAcc, Pass).
'_expand_goal'((G1->G2;G3), (TG1->TG2;TG3), NaAr, HList, Acc, NewAcc, Pass) :-
    '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass),
    '_expand_goal'(G2, MG2, NaAr, HList, MidAcc, Acc1, Pass),
    '_expand_goal'(G3, MG3, NaAr, HList, Acc, Acc2, Pass),
    '_merge_acc'(Acc, Acc1, MG2, TG2, Acc2, MG3, TG3, NewAcc).
'_expand_goal'((G1*->G2;G3), (TG1*->TG2;TG3), NaAr, HList, Acc, NewAcc, Pass) :-
    '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass),
    '_expand_goal'(G2, MG2, NaAr, HList, MidAcc, Acc1, Pass),
    '_expand_goal'(G3, MG3, NaAr, HList, Acc, Acc2, Pass),
    '_merge_acc'(Acc, Acc1, MG2, TG2, Acc2, MG3, TG3, NewAcc).
'_expand_goal'((G1;G2), (TG1;TG2), NaAr, HList, Acc, NewAcc, Pass) :-
    '_expand_goal'(G1, MG1, NaAr, HList, Acc, Acc1, Pass),
    '_expand_goal'(G2, MG2, NaAr, HList, Acc, Acc2, Pass),
    '_merge_acc'(Acc, Acc1, MG1, TG1, Acc2, MG2, TG2, NewAcc).
'_expand_goal'((G1->G2), (TG1->TG2), NaAr, HList, Acc, NewAcc, Pass) :-
    '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass),
    '_expand_goal'(G2, TG2, NaAr, HList, MidAcc, NewAcc, Pass).
'_expand_goal'((G1*->G2), (TG1->TG2), NaAr, HList, Acc, NewAcc, Pass) :-
    '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass),
    '_expand_goal'(G2, TG2, NaAr, HList, MidAcc, NewAcc, Pass).
'_expand_goal'((\+G), (\+TG), NaAr, HList, Acc, Acc, Pass) :-
    '_expand_goal'(G, TG, NaAr, HList, Acc, _TempAcc, Pass).
'_expand_goal'({G}, G, _, _, Acc, Acc, _) :- !.
'_expand_goal'(insert(X,Y), LeftA=X, _, _, Acc, NewAcc, _) :-
    '_replace_acc'(dcg, LeftA, RightA, Y, RightA, Acc, NewAcc), !.
'_expand_goal'(insert(X,Y):A, LeftA=X, _, _, Acc, NewAcc, _) :-
    '_replace_acc'(A, LeftA, RightA, Y, RightA, Acc, NewAcc),
    debug(edcg,'Expanding accumulator goal: ~w',[insert(X,Y):A]),
    !.
% Force hidden arguments in L to be appended to G:
'_expand_goal'((G:A), TG, _, _HList, Acc, NewAcc, Pass) :-
    \+'_list'(G),
    '_has_hidden'(G, []), !,
    '_make_list'(A, AList),
    '_new_goal'(G, AList, GArity, TG),
    '_use_acc_pass'(AList, GArity, TG, Acc, NewAcc, Pass).
% Use G's regular hidden arguments & override defaults for those arguments
% not in the head:
'_expand_goal'((G:A), TG, _, _HList, Acc, NewAcc, Pass) :-
    \+'_list'(G),
    '_has_hidden'(G, GList), GList\==[], !,
    '_make_list'(A, L),
    '_new_goal'(G, GList, GArity, TG),
    '_replace_defaults'(GList, NGList, L),
    '_use_acc_pass'(NGList, GArity, TG, Acc, NewAcc, Pass).
'_expand_goal'((L:A), Joiner, NaAr, _, Acc, NewAcc, _) :-
    '_list'(L), !,
    '_joiner'(L, A, NaAr, Joiner, Acc, NewAcc).
'_expand_goal'(L, Joiner, NaAr, _, Acc, NewAcc, _) :-
    '_list'(L), !,
    '_joiner'(L, dcg, NaAr, Joiner, Acc, NewAcc).
'_expand_goal'((X/A), true, _, _, Acc, Acc, _) :-
    atomic(A),
    member(acc(A,X,_), Acc),
    debug(edcg,'Expanding accumulator goal: ~w',[X/A]),
    !.
'_expand_goal'((X/A), true, _, _, Acc, Acc, Pass) :-
    atomic(A),
    member(pass(A,X), Pass),
    debug(edcg,'Expanding passed argument goal: ~w',[X/A]),
    !.
'_expand_goal'((A/X), true, _, _, Acc, Acc, _) :-
    atomic(A),
    member(acc(A,_,X), Acc), !.
'_expand_goal'((X/A/Y), true, _, _, Acc, Acc, _) :-
    var(X), var(Y), atomic(A),
    member(acc(A,X,Y), Acc), !.
'_expand_goal'((X/Y), true, NaAr, _, Acc, Acc, _) :-
    print_message(warning,missing_hidden_parameter(NaAr,X/Y)).
% Defaulty cases:
'_expand_goal'(G, TG, _HList, _, Acc, NewAcc, Pass) :-
    '_has_hidden'(G, GList), !,
    '_new_goal'(G, GList, GArity, TG),
    '_use_acc_pass'(GList, GArity, TG, Acc, NewAcc, Pass).

% ==== The following was originally acc-pass.pl ====

% Operations on the Acc and Pass data structures:

% Create the Acc and Pass data structures:
% Acc contains terms of the form acc(A,LeftA,RightA) where A is the name of an
% accumulator, and RightA and LeftA are the accumulating parameters.
% Pass contains terms of the form pass(A,Arg) where A is the name of a passed
% argument, and Arg is the argument.
'_create_acc_pass'([], _, _, [], []).
'_create_acc_pass'([A|AList], Index, TGoal, [acc(A,LeftA,RightA)|Acc], Pass) :-
    '_is_acc'(A), !,
    Index1 is Index+1,
    arg(Index1, TGoal, LeftA),
    Index2 is Index+2,
    arg(Index2, TGoal, RightA),
    '_create_acc_pass'(AList, Index2, TGoal, Acc, Pass).
'_create_acc_pass'([A|AList], Index, TGoal, Acc, [pass(A,Arg)|Pass]) :-
    '_is_pass'(A), !,
    Index1 is Index+1,
    arg(Index1, TGoal, Arg),
    '_create_acc_pass'(AList, Index1, TGoal, Acc, Pass).
'_create_acc_pass'([A|_AList], _Index, _TGoal, _Acc, _Pass) :-
    \+'_is_acc'(A),
    \+'_is_pass'(A),
    print_message(error,not_a_hidden_param(A)).


% Use the Acc and Pass data structures to create the arguments of a body goal:
% Add the hidden parameters named in GList to the goal.
'_use_acc_pass'([], _, _, Acc, Acc, _).
% 1a. The accumulator A is used in the head:
'_use_acc_pass'([A|GList], Index, TGoal, Acc, NewAcc, Pass) :-
    '_replace_acc'(A, LeftA, RightA, MidA, RightA, Acc, MidAcc), !,
    Index1 is Index+1,
    arg(Index1, TGoal, LeftA),
    Index2 is Index+2,
    arg(Index2, TGoal, MidA),
    '_use_acc_pass'(GList, Index2, TGoal, MidAcc, NewAcc, Pass).
% 1b. The accumulator A is not used in the head:
'_use_acc_pass'([A|GList], Index, TGoal, Acc, NewAcc, Pass) :-
    '_acc_info'(A, LStart, RStart), !,
    Index1 is Index+1,
    arg(Index1, TGoal, LStart),
    Index2 is Index+2,
    arg(Index2, TGoal, RStart),
    '_use_acc_pass'(GList, Index2, TGoal, Acc, NewAcc, Pass).
% 2a. The passed argument A is used in the head:
'_use_acc_pass'([A|GList], Index, TGoal, Acc, NewAcc, Pass) :-
    '_is_pass'(A),
    member(pass(A,Arg), Pass), !,
    Index1 is Index+1,
    arg(Index1, TGoal, Arg),
    '_use_acc_pass'(GList, Index1, TGoal, Acc, NewAcc, Pass).
% 2b. The passed argument A is not used in the head:
'_use_acc_pass'([A|GList], Index, TGoal, Acc, NewAcc, Pass) :-
    '_pass_info'(A, AStart), !,
    Index1 is Index+1,
    arg(Index1, TGoal, AStart),
    '_use_acc_pass'(GList, Index1, TGoal, Acc, NewAcc, Pass).
% 3. Defaulty case when A does not exist:
'_use_acc_pass'([A|_GList], _Index, _TGoal, Acc, Acc, _Pass) :-
    print_message(error,not_a_hidden_param(A)).

% Finish the Acc data structure:
% Link its Left and Right accumulation variables together in pairs:
% TODO: does this work correctly in the presence of cuts? ("!") - see README
'_finish_acc'([]).
'_finish_acc'([acc(_,Link,Link)|Acc]) :- '_finish_acc'(Acc).

'_finish_acc_ssu'([], TB, TB).
'_finish_acc_ssu'([acc(_,Link0,Link1)|Acc], TB0, TB) :-
    '_finish_acc_ssu'(Acc, (Link0=Link1,TB0), TB).

% Replace elements in the Acc data structure:
% Succeeds iff replacement is successful.
'_replace_acc'(A, L1, R1, L2, R2, Acc, NewAcc) :-
    member(acc(A,L1,R1), Acc), !,
    '_replace'(acc(A,_,_), acc(A,L2,R2), Acc, NewAcc).

% Combine two accumulator lists ('or'ing their values)
'_merge_acc'([], [], G1, G1, [], G2, G2, []) :- !.
'_merge_acc'([acc(Acc,OL,R)|Accs], [acc(Acc,L1,R)|Accs1], G1, NG1,
         [acc(Acc,L2,R)|Accs2], G2, NG2, [acc(Acc,NL,R)|NewAccs]) :- !,
    ( ( OL == L1, OL \== L2 ) ->
      MG1 = (G1,L1=L2), MG2 = G2, NL = L2
        ; ( OL == L2, OL \== L1 ) ->
      MG2 = (G2,L2=L1), MG1 = G1, NL = L1
        ; MG1 = G1, MG2 = G2, L1 = L2, L2 = NL ),
    '_merge_acc'(Accs, Accs1, MG1, NG1, Accs2, MG2, NG2, NewAccs).

% ==== The following was originally generic-util.pl ====

% Generic utilities special-util.pl

% Match arguments L, L+1, ..., H of the predicates P and Q:
'_match'(L, H, _, _) :- L>H, !.
'_match'(L, H, P, Q) :- L=<H, !,
    arg(L, P, A),
    arg(L, Q, A),
    L1 is L+1,
    '_match'(L1, H, P, Q).


'_list'(L) :- nonvar(L), L=[_|_], !.
'_list'(L) :- L==[], !.

'_make_list'(A, [A]) :- \+'_list'(A), !.
'_make_list'(L,   L) :-   '_list'(L), !.

% replace(Elem, RepElem, List, RepList)
'_replace'(_, _, [], []).
'_replace'(A, B, [A|L], [B|R]) :- !,
    '_replace'(A, B, L, R).
'_replace'(A, B, [C|L], [C|R]) :-
    \+C=A, !,
    '_replace'(A, B, L, R).

% ==== The following was originally special-util.pl ====

% Specialized utilities:

% Given a goal Goal and a list of hidden parameters GList
% create a new goal TGoal with the correct number of arguments.
% Also return the arity of the original goal.
'_new_goal'(Goal, GList, GArity, TGoal) :-
    functor(Goal, Name, GArity),
    '_number_args'(GList, GArity, TArity),
    functor(TGoal, Name, TArity),
    '_match'(1, GArity, Goal, TGoal).

% Add the number of arguments needed for the hidden parameters:
'_number_args'([], N, N).
'_number_args'([A|List], N, M) :-
    '_is_acc'(A), !,
    N2 is N+2,
    '_number_args'(List, N2, M).
'_number_args'([A|List], N, M) :-
    '_is_pass'(A), !,
    N1 is N+1,
    '_number_args'(List, N1, M).
'_number_args'([_|List], N, M) :- !,
    % error caught elsewhere
    '_number_args'(List, N, M).

% Give a list of G's hidden parameters:
'_has_hidden'(G, GList) :-
    functor(G, GName, GArity),
    (   client_module(Client), Client:pred_info(GName, GArity, GList)
    ;   /*edcg:pred_info(GName, GArity, GList)
    ->  true
    ;   */GList=[]
    ).
/*
'_has_hidden'(G, GList) :-
    functor(G, GName, GArity),
    pred_info(GName, GArity, GList).
'_has_hidden'(G, []) :-
    functor(G, GName, GArity),
    \+pred_info(GName, GArity, _).
*/

% Succeeds if A is an accumulator:
'_is_acc'(A)  :- atomic(A), !, '_acc_info'(A, _, _, _, _, _, _).
'_is_acc'(A)  :- functor(A, N, 2), !, '_acc_info'(N, _, _, _, _, _, _).

% Succeeds if A is a passed argument:
'_is_pass'(A) :- atomic(A), !, '_pass_info'(A, _).
'_is_pass'(A) :- functor(A, N, 1), !, '_pass_info'(N, _).

% Get initial values for the accumulator:
'_acc_info'(AccParams, LStart, RStart) :-
    functor(AccParams, Acc, 2),
    '_is_acc'(Acc), !,
    arg(1, AccParams, LStart),
    arg(2, AccParams, RStart).
'_acc_info'(Acc, LStart, RStart) :-
    '_acc_info'(Acc, _, _, _, _, LStart, RStart).

% Isolate the internal database from the user database:
'_acc_info'(Acc, Term, Left, Right, Joiner, LStart, RStart) :-
    client_module(M),
    current_predicate(M:acc_info/7),
    M:acc_info(Acc, Term, Left, Right, Joiner, LStart, RStart).
    %edcg:*/acc_info(Acc, Term, Left, Right, Joiner, LStart, RStart).
'_acc_info'(Acc, Term, Left, Right, Joiner, _, _) :-
    client_module(M),
    current_predicate(M:acc_info/5),
    M:acc_info(Acc, Term, Left, Right, Joiner).
    %edcg:*/acc_info(Acc, Term, Left, Right, Joiner).
'_acc_info'(dcg, Term, Left, Right, Left=[Term|Right], _, []).

% Get initial value for the passed argument:
% Also, isolate the internal database from the user database.
'_pass_info'(PassParam, PStart) :-
    functor(PassParam, Pass, 1),
    '_is_pass'(Pass), !,
    arg(1, PassParam, PStart).
'_pass_info'(Pass, PStart) :-
    client_module(M),
    current_predicate(M:pass_info/2),
    M:pass_info(Pass, PStart).
    %edcg:*/pass_info(Pass, PStart).
'_pass_info'(Pass, _) :-
    client_module(M),
    current_predicate(M:pass_info/1),
    M:pass_info(Pass).
    %edcg:*/pass_info(Pass).

% Calculate the joiner for an accumulator A:
'_joiner'([], _, _, true, Acc, Acc).
'_joiner'([Term|List], A, NaAr, (Joiner,LJoiner), Acc, NewAcc) :-
    '_replace_acc'(A, LeftA, RightA, MidA, RightA, Acc, MidAcc),
    '_acc_info'(A, Term, LeftA, MidA, Joiner, _, _), !,
    '_joiner'(List, A, NaAr, LJoiner, MidAcc, NewAcc).
% Defaulty case:
'_joiner'([_Term|List], A, NaAr, Joiner, Acc, NewAcc) :-
    print_message(warning, missing_accumulator(NaAr,A)),
    '_joiner'(List, A, NaAr, Joiner, Acc, NewAcc).

% Replace hidden parameters with ones containing initial values:
'_replace_defaults'([], [], _).
'_replace_defaults'([A|GList], [NA|NGList], AList) :-
    '_replace_default'(A, NA, AList),
    '_replace_defaults'(GList, NGList, AList).

'_replace_default'(A, NewA, AList) :-  % New initial values for accumulator.
    functor(NewA, A, 2),
    member(NewA, AList), !.
'_replace_default'(A, NewA, AList) :-  % New initial values for passed argument.
    functor(NewA, A, 1),
    member(NewA, AList), !.
'_replace_default'(A, NewA, _) :-      % Use default initial values.
    A=NewA.

% ==== The following was originally messages.pl ====

:- multifile prolog:message//1.

prolog:message(missing_accumulator(Predicate,Accumulator)) -->
    ['In ~w the accumulator ''~w'' does not exist'-[Predicate,Accumulator]].
prolog:message(missing_hidden_parameter(Predicate,Term)) -->
    ['In ~w the term ''~w'' uses a non-existent hidden parameter.'-[Predicate,Term]].
prolog:message(not_a_hidden_param(Name)) -->
    ['~w is not a hidden parameter'-[Name]].

end_of_file.

So far, it seems to work, but I think it doesn’t solve OP’s requirements anyway…

?- print_solutions.
[ [ ([goose]:hither->yon:state{beans:hither,farmer:yon,fox:hither,goose:yon}),
    ([]:yon->hither:state{beans:hither,farmer:hither,fox:hither,goose:yon}),
    ([beans]:hither->yon:state{beans:yon,farmer:yon,fox:hither,goose:yon}),
    ([goose]:yon->hither:state{beans:yon,farmer:hither,fox:hither,goose:hither}),
    ([fox]:hither->yon:state{beans:yon,farmer:yon,fox:yon,goose:hither}),
    ([]:yon->hither:state{beans:yon,farmer:hither,fox:yon,goose:hither}),
    ([goose]:hither->yon:state{beans:yon,farmer:yon,fox:yon,goose:yon})
  ],
  [ ([goose]:hither->yon:state{beans:hither,farmer:yon,fox:hither,goose:yon}),
    ([]:yon->hither:state{beans:hither,farmer:hither,fox:hither,goose:yon}),
    ([fox]:hither->yon:state{beans:hither,farmer:yon,fox:yon,goose:yon}),
    ([goose]:yon->hither:state{beans:hither,farmer:hither,fox:yon,goose:hither}),
    ([beans]:hither->yon:state{beans:yon,farmer:yon,fox:yon,goose:hither}),
    ([]:yon->hither:state{beans:yon,farmer:hither,fox:yon,goose:hither}),
    ([goose]:hither->yon:state{beans:yon,farmer:yon,fox:yon,goose:yon})
  ]
]
true.
1 Like

That I think is the right direction. I think we could define it like this (only for one of the preds):

'_acc_info'(Acc, Term, Left, Right, Joiner, LStart, RStart) :-
   prolog_load_context(module, M),
   current_predicate(M:acc_info/7),
   M:acc_info(Acc, Term, Left, Right, Joiner, LStart, RStart).
'_acc_info'(Acc, Term, Left, Right, Joiner, LStart, RStart) :-
    acc_info(Acc, Term, Left, Right, Joiner, LStart, RStart).

Like that, local rules will always overrule global ones and global ones can be defined as usual in the edcg module.

1 Like

I’ve prepared a PR, seems it’s backward compatible, since every test from edcg/t pass.
In t-modular I copied the files from t, and edited blindly removing the (now uncessary) edcg: prefixes, but there are 2 test in t-modular/examples.pl I cannot get to work:

Warning: c:/users/carlo/develop/swipl/edcg/t-modular/examples.pl:223:
Warning:    Clauses of plunit_edcg_examples:pred_info/3 are not together in the source-file
Warning:    Earlier definition at c:/users/carlo/develop/swipl/edcg/t-modular/examples.pl:213
Warning:    Current predicate: plunit_edcg_examples:'unit body'/2
Warning:    Use :- discontiguous plunit_edcg_examples:pred_info/3. to suppress this message
Welcome to SWI-Prolog (threaded, 64 bits, version 8.5.7)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- run_tests.
% PL-Unit: edcg_examples ..............
ERROR: c:/users/carlo/develop/swipl/edcg/t-modular/examples.pl:215:
        test ssu1: wrong answer (compared using =@=)
ERROR:     Expected: p(b,A,B,C)=>B=C,A=2
ERROR:     Got:      p(b,A)=>A=2
ERROR: c:/users/carlo/develop/swipl/edcg/t-modular/examples.pl:225:
        test ssu2_guard: wrong answer (compared using =@=)
ERROR:     Expected: p2(A,B,C,D),A=a=>C=D,B=1
ERROR:     Got:      p2(A,B),A=a=>B=1
 done
% 2 tests failed
% 14 tests passed
false.

Both are related to SSU extension, do you have an hint for solving them ? Or I could issue the PR anyway ?

edit

In t-modular/examples.pl now there are 2 declarations user:pred_info, made necessary because embedded in :- begin_tests. These allow to pass all tests, but I don’t understand the motivation. Anyway, the PR could be demeed complete now ?

I was also doing a little holiday hacking. Came with GitHub - JanWielemaker/edcg at module which fixes this test as well. The problem is that expand_term/2 doesn’t activate the prolog_load_context(module, M). I’ve worked around that by expanding and retrieving the SSU rule.

All in all I think this is a workable solution but I’m not sure this is the optional one. A more sensible solution might be to introduce the specification using directives.

I’m wondering what people think about edcg. Is it a viable way to deal with passing arguments and creating accumulators? Is it worthwhile modernizing it (modules, declarations, integration with the IDE) and make it a standard library? What are the alternatives?

I think EDCG is a great generalization on DCGs, and ought to become built-in, but I’m biased. :wink:
But not in its current form.

My main complaints about it are:

  • requires declarations before use, so the information about what accumulators are used with a predicate are separate from the predicate definition (2 passes would solve this)
    • I’m dubious about the “pattern-matching” style of declarations; I think exact specification is better, e.g. foo(X)//[accum1,accum2] -->> ....
  • lack of listing/1 and debugger support; lack of // notation support
  • doesn’t handle modules
  • weird cross-module behavior (as others have noted)
1 Like

Yes.

However I only use it when there are more then 3 accumulators (6 arguments) as managing 3 accumulators by hand is not hard.

I see no rush for this just yet but in the long run it would be good thing.

Just keep using as it is but adding documentation on how to use it.

It is not something one just adds in and then starts running like any other simple predicates. For me I literally had to read the papers figure out how to create test cases out of the examples and then get the same results. Then had to instrument the code to understand some of the rewrites which took a few more days. That does not even count the days I was lost in trying to understand how SWI-Prolog worked internally to do the rewrite code.