Meta interpreter for tracking state?

(Copying over from my reddit post. Same question plz)

Hey can you help me out with how this can be done?

Suppose I’m doing something like this

hooray :- writeln("hooray").

rand_check :-
  random_permutation([1,2,3],L),
  random_between(0,2,Position),
  random_between(1,3,Target),
  nth0(Position,L,Target).

foo :-
  rand_check,
  hooray, !
  ;
  writeln("missed foo").

bar_(0) :- !.
bar_(N) :-
  ( rand_check,
    hooray, !
    ;
    writeln("missed bar")
  ),
  succ(N0,N),
  bar_(N0).

bar :-
  bar_(20).

main :-
  foo,
  bar.

but let’s say in addition to printing “hooray”, I want to gain 1 point for each hit and then return my total points when the program completes.

One way to do this would be to thread a point accumulator throughout my entire program

hooray :- writeln("hooray").

rand_check :-
  random_permutation([1,2,3],L),
  random_between(0,2,Position),
  random_between(1,3,Target),
  nth0(Position,L,Target).

foo(P0,P1) :-
  rand_check,
  hooray,
  P1 is P0+1, !
  ;
  P1 = P0,
  writeln("missed foo").

bar_(0,P,P) :- !.
bar_(N,P1,P2) :-
  ( rand_check,
    hooray,
    P3 is P1+1, !
    ;
    P3 is P1+0,
    writeln("missed bar")
  ),
  succ(N0,N),
  bar_(N0,P3,P2).

bar(P1,P2) :-
  bar_(20,P1,P2).

main :-
  P0 = 0,
  foo(P0,P1),
  bar(P1,P2),
  write("Score: "),
  writeln(P2).

but I think the problems with this are obvious? Inconvenient to implement, doesn’t scale well, error prone, muddies up your code with all the extra variables, etc. Not ideal, especially for larger apps.

You can also use a mutable flag

hooray :- writeln("hooray").
add_point :- flag(points,P,P+1).

rand_check :-
  random_permutation([1,2,3],L),
  random_between(0,2,Position),
  random_between(1,3,Target),
  nth0(Position,L,Target).

foo :-
  rand_check,
  hooray,
  add_point, !
  ;
  writeln("missed foo").

bar_(0) :- !.
bar_(N) :-
  ( rand_check,
    hooray,
    add_point, !
    ;
    writeln("missed bar")
  ),
  succ(N0,N),
  bar_(N0).

bar :-
  bar_(20).

main :-
  set_flag(points,0),
  foo,
  bar,
  get_flag(points,P),
  write("Score: "),
  writeln(P).

Better, cleaner, but I feel like you’re still muddying up your code a little bit by sprinkling calls to your flag all over, and also what if you prefer a more immutable solution? A flag is basically just a mutable variable, thread-safe though it is.

Tell me if this is crazy but I was wondering if an MCI could be used to completely decouple the point tracking system from the actual logic, ie. the first code block above.

If so, what would that look like? If not, is there a better way to do this? Thanks.

The important detail is whether you need to meta-interpret Prolog or some subset of it. If you need to meta-interpret any valid Prolog code, this is gong to be quite some effort. If you can find some sub-set that fulfils your needs, it sounds much more doable.

Even for such a small example like this? I’m still reviewing this article and the accompanying video but I really would like to see what the mi would look like for exactly my example above.

This textbook is extremely terse, to a fault. It shows how to do some nifty tricks but doesn’t really teach in the classical meaning of the word.

If you wish to “thread” an accumulator through your code, DCGs can do that, but the normal implementation is to thread a list (some implementations let you redefined the 'C' that accumulates the list). There’s pack(edcg), which has an example of a counting accumulator in the referenced paper, which is also in edcg/t/examples.pl (predicate expr_code):

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

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