/*  Showcase examples for apl.pl

This file is aimed at someone seeing `apl.pl` for the first time.

The examples are chosen to show the mix of:

  - APL-style array notation
  - Prolog relations and backtracking
  - CLP(Z) constraints
  - user-defined verbs
  - env refs for reading and exporting values

Load it with:

  ==
  ?- use_module('./apl.pl').
  ?- use_module('./apl_showcase.pl').
  ==

Then either run the full tour:

  ==
  ?- run_showcase.
  ==

Or try the examples one by one:

  ==
  ?- apl('+/ {3 3} # i 10', R).
  R = [9,12,15].

  ?- apl('(1 + i 10) % 3 e 0 m (1 + i 10)', R).
  R = [3,6,9].

  ?- apl('^mask (1 + i 10) % 3 e 0 & ?mask m (1 + i 10)',
         [mask=Mask],
         R).
  R = [3,6,9],
  Mask = [0,0,1,0,0,1,0,0,1,0].

  ?- apl('square i 10', [module=apl_showcase], R).
  R = [0,1,4,9,16,25,36,49,64,81].

  ?- apl('10 step 1 + i 5', [module=apl_showcase], R).
  R = [12,14,16,18,20].

  ?- findall(C, apl('cubes', [module=apl_showcase], C), Cs).
  Cs = [1,8,27,64].

  ?- findall(X, apl('0 between 5', [module=apl_showcase], X), Xs).
  Xs = [0,1,2,3,4,5].

  ?- apl('?x : 3 + 4 & ?y : ?x * 10 & {?x ?y}',
         [x=X, y=Y],
         R).
  R = [7,70],
  X = 7,
  Y = 70.

  ?- apl('?f i 6', [module=apl_showcase, f=square], R).
  R = [0,1,4,9,16,25].

  ?- apl('+\\ (1 + i 6)', R).
  R = [1,3,6,10,15,21].

  ?- apl('{3 4} # {0 1}', R).
  R = [[0,1,0,1],[0,1,0,1],[0,1,0,1]].

  ?- apl('transpose ({2 3} # i 10)', R).
  R = [[0,3],[1,4],[2,5]].

  ?- apl('^vals 1 + i 10 & ?vals @ where (?vals % 3 e 0)',
         [vals=Vals],
         R).
  R = [3,6,9],
  Vals = [1,2,3,4,5,6,7,8,9,10].

  ?- apl('parity 1 + i 8', [module=apl_showcase], R).
  R = [odd,even,odd,even,odd,even,odd,even].

  ?- apl('?x + 3 : 10 & ?x', [x=X], R).
  R = 7,
  X = 7.

  ?- apl('?lhs = ?rhs', [lhs=pair(X,2), rhs=pair(1,Y)], R).
  R = pair(1,2),
  X = 1,
  Y = 2.

  ?- apl('?f ?terms',
         [module=apl_showcase, f=term_arity, terms=[foo(1),pair(a,b),q]],
         R).
  R = [1,2,0].

  ?- apl('term_arity ?terms',
         [module=apl_showcase, terms=[foo(1),pair(a,b),q]],
         R).
  R = [1,2,0].

  ?- apl_ast('square i 6', AST),
     AST = monad(ident(square), Arg),
     apl_eval(monad(ident(inc), Arg), [module=apl_showcase], R).
  AST = monad(ident(square),monad(i,num(6))),
  R = [1,2,3,4,5,6].

  ?- apl('?xs @ 1 = ?pat',
         [xs=[add(1,2),mul(3,4)], pat=mul(A,B)],
         R).
  R = mul(3,4),
  A = 3,
  B = 4.

  ?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps).
  Ps = [[1,9],[2,8],[3,7],[4,6],[5,5],[6,4],[7,3],[8,2],[9,1]].

  ?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps),
     apl('+/ ?pairs', [pairs=Ps], R).
  Ps = [[1,9],[2,8],[3,7],[4,6],[5,5],[6,4],[7,3],[8,2],[9,1]],
  R = [45,45].

  ?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps),
     apl('+\\ ?pairs', [pairs=Ps], R).
  R = [[1,9],[3,17],[6,24],[10,30],[15,35],[21,39],[28,42],[36,44],[45,45]].

  ?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps),
     apl('transpose ?pairs', [pairs=Ps], R).
  R = [[1,2,3,4,5,6,7,8,9],[9,8,7,6,5,4,3,2,1]].

  ?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps),
     apl('^left 0 col ?pairs & ?left % 2 e 0 m ?pairs',
         [pairs=Ps],
         R).
  R = [[2,8],[4,6],[6,4],[8,2]].

  ?- findall(T, apl('pythag 20', [module=apl_showcase], T), Ts),
     apl('parity ?triples', [module=apl_showcase, triples=Ts], R).
  Ts = [[3,4,5],[5,12,13],[6,8,10],[8,15,17],[9,12,15],[12,16,20]],
  R = [[odd,even,odd],[odd,even,odd],[even,even,even],[even,odd,odd],[odd,even,odd],[even,even,even]].

  ?- findall(T, apl('pythag 20', [module=apl_showcase], T), Ts),
     apl('^hyp 2 col ?triples & ?hyp % 2 e 0 m ?triples',
         [triples=Ts],
         R).
  R = [[6,8,10],[12,16,20]].

  ?- apl('?xs @ {2 0}', [xs=[foo(1),bar(2),baz(3)]], R).
  R = [baz(3),foo(1)].

  ?- findall(X, apl_goal('(?x = 5) | (?x = 8)', [x=X]), Xs).
  Xs = [5,8].
  ==

The point is not to replace Prolog. The point is to get a compact array
surface while keeping Prolog's strongest features available underneath.
*/

:- module(apl_showcase, [
    run_showcase/0,
    inc/2,
    parity/2,
    square/2,
    step/3,
    cubes/1,
    term_arity/2,
    pair_sum/2,
    pythag/2
]).

:- use_module(library(clpz)).
:- use_module(library(between)).
:- use_module('./apl.pl').

%% inc(+X, -Y) is det.
%
%  Example monadic host verb.
inc(X, Y) :-
    Y #= X + 1.

%% square(+X, -Y) is det.
%
%  Example monadic host verb.
square(X, Y) :-
    Y #= X * X.

%% parity(+X, -Parity) is det.
%
%  Example monadic host verb returning symbolic values.
parity(X, Parity) :-
    ( 0 is X mod 2 ->
        Parity = even
    ;
        Parity = odd
    ).

%% step(+Left, +Right, -Result) is det.
%
%  Example dyadic host verb.
step(Left, Right, Result) :-
    Result #= Left + (2 * Right).

%% cubes(-Value) is nondet.
%
%  Example bare generator returning one value on each solution.
cubes(Value) :-
    range(1, 4, N),
    Value #= N * N * N.

%% term_arity(+Term, -Arity) is det.
%
%  Example symbolic host verb for arrays of ordinary Prolog terms.
term_arity(Term, Arity) :-
    functor(Term, _, Arity).

%% pair_sum(+Target, -Pair) is nondet.
%
%  Enumerate positive pairs that sum to Target.
pair_sum(Target, [X, Y]) :-
    integer(Target),
    Upper is Target - 1,
    range(1, Upper, X),
    Y #= Target - X.

%% pythag(+Max, -Triple) is nondet.
%
%  Enumerate Pythagorean triples with sides up to Max.
pythag(Max, [A, B, C]) :-
    integer(Max),
    range(1, Max, A),
    range(A, Max, B),
    range(B, Max, C),
    A * A + B * B #= C * C.

%% run_showcase is det.
%
%  Print a short guided tour of the showcase examples.
run_showcase :-
    once(run_showcase_steps).

run_showcase_steps :-
    title('Relational APL Showcase'),
    section('1. APL array code, Prolog values out'),
    show_apl('+/ {3 3} # i 10'),

    section('2. Boolean mask + selection'),
    show_apl('(1 + i 10) % 3 e 0 m (1 + i 10)'),

    section('3. Export an intermediate value without disturbing the pipeline'),
    ExportExpr = '^mask (1 + i 10) % 3 e 0 & ?mask m (1 + i 10)',
    apl(ExportExpr,
        [mask=Mask],
        Filtered),
    format("?- apl(~q, [mask=Mask], R).~n", [ExportExpr]),
    format("R = ~q,~nMask = ~q.~n~n", [Filtered, Mask]),

    section('4. User-defined monad pervading over an array'),
    show_apl('square i 10', [module=apl_showcase]),

    section('5. User-defined dyad with scalar-left / vector-right pervasion'),
    show_apl('10 step 1 + i 5', [module=apl_showcase]),

    section('6. Bare identifiers can be Prolog generators'),
    findall(C, apl('cubes', [module=apl_showcase], C), Cs),
    show_query("findall(C, apl('cubes', [module=apl_showcase], C), Cs)", Cs),

    section('7. Any loaded Prolog relation can be used as a verb'),
    findall(X, apl('0 between 5', [module=apl_showcase], X), Xs1),
    show_query("findall(X, apl('0 between 5', [module=apl_showcase], X), Xs)", Xs1),

    section('8. Logic variables and CLP(Z) constraints inside APL'),
    ConstraintExpr = '?x : 3 + 4 & ?y : ?x * 10 & {?x ?y}',
    apl(ConstraintExpr,
        [x=X, y=Y],
        Pair),
    format("?- apl(~q, [x=X, y=Y], R).~n", [ConstraintExpr]),
    format("R = ~q,~nX = ~q,~nY = ~q.~n~n", [Pair, X, Y]),

    section('9. Higher-order style: pick the verb from the environment'),
    show_apl('?f i 6', [module=apl_showcase, f=square]),

    section('10. Scan gives you a progressive state trace'),
    show_apl('+\\ (1 + i 6)'),

    section('11. Reshape can cycle short data to fill a larger array'),
    show_apl('{3 4} # {0 1}'),

    section('12. Bundled transpose can flip a matrix without index arithmetic'),
    show_apl('transpose ({2 3} # i 10)'),

    section('13. Bundled where gives pick indices from a computed mask'),
    WhereExpr = '^vals 1 + i 10 & ?vals @ where (?vals % 3 e 0)',
    apl(WhereExpr,
        [vals=Vals],
        Multiples),
    format("?- apl(~q, [vals=Vals], R).~n", [WhereExpr]),
    format("R = ~q,~nVals = ~q.~n~n", [Multiples, Vals]),

    section('14. Host verbs can return symbolic terms, not just numbers'),
    show_apl('parity 1 + i 8', [module=apl_showcase]),

    section('15. A relation can run backwards to solve for a missing input'),
    apl('?x + 3 : 10 & ?x',
        [x=XInv],
        Solved),
    format("?- apl('?x + 3 : 10 & ?x', [x=X], R).~n", []),
    format("R = ~q,~nX = ~q.~n~n", [Solved, XInv]),

    section('16. Prolog unification works inside APL expressions'),
    apl('?lhs = ?rhs',
        [lhs=pair(XL, 2), rhs=pair(1, YL)],
        Unified),
    format("?- apl('?lhs = ?rhs', [lhs=pair(X,2), rhs=pair(1,Y)], R).~n", []),
    format("R = ~q,~nX = ~q,~nY = ~q.~n~n", [Unified, XL, YL]),

    section('17. The active verb can come from the environment'),
    show_apl('?f ?terms',
             [module=apl_showcase, f=term_arity, terms=[foo(1),pair(a,b),q]]),

    section('18. Symbolic Prolog verbs can map across arrays of terms'),
    show_apl('term_arity ?terms',
             [module=apl_showcase, terms=[foo(1),pair(a,b),q]]),

    section('19. APL code is a Prolog term, so you can rewrite it'),
    apl_ast('square i 6', AST0),
    AST0 = monad(ident(square), Arg),
    AST1 = monad(ident(inc), Arg),
    apl_eval(AST1, [module=apl_showcase], Rewritten),
    format("?- apl_ast('square i 6', AST), AST = monad(ident(square), Arg),~n", []),
    format("   apl_eval(monad(ident(inc), Arg), [module=apl_showcase], R).~n", []),
    format("AST = ~q,~nR = ~q.~n~n", [AST0, Rewritten]),

    section('20. You can pull a term out of an array and pattern-match it'),
    apl('?xs @ 1 = ?pat',
        [xs=[add(1,2),mul(3,4)], pat=mul(A, B)],
        Picked),
    format("?- apl('?xs @ 1 = ?pat', [xs=[add(1,2),mul(3,4)], pat=mul(A,B)], R).~n", []),
    format("R = ~q,~nA = ~q,~nB = ~q.~n~n", [Picked, A, B]),

    section('21. Finite-domain search can generate arrays of solutions'),
    findall(PairFD, apl('pair_sum 10', [module=apl_showcase], PairFD), Pairs),
    show_query("findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps)", Pairs),

    section('22. Collected search results become a matrix you can reduce'),
    apl('+/ ?pairs', [pairs=Pairs], PairSums),
    format("?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps),~n", []),
    format("   apl('+/ ?pairs', [pairs=Ps], R).~n", []),
    format("Ps = ~q,~nR = ~q.~n~n", [Pairs, PairSums]),

    section('23. The same matrix can be transposed into explicit columns'),
    apl('transpose ?pairs', [pairs=Pairs], PairColumns),
    format("?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps),~n", []),
    format("   apl('transpose ?pairs', [pairs=Ps], R).~n", []),
    format("R = ~q.~n~n", [PairColumns]),

    section('24. The same matrix can be scanned to get running column totals'),
    apl('+\\ ?pairs', [pairs=Pairs], PairScan),
    format("?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps),~n", []),
    format("   apl('+\\\\ ?pairs', [pairs=Ps], R).~n", []),
    format("R = ~q.~n~n", [PairScan]),

    section('25. Bundled col/3 removes ravel index arithmetic in row filters'),
    PairMaskExpr =
        '^left 0 col ?pairs & ?left % 2 e 0 m ?pairs',
    apl(PairMaskExpr,
        [pairs=Pairs, left=_Left],
        EvenLeftPairs),
    format("?- findall(P, apl('pair_sum 10', [module=apl_showcase], P), Ps),~n", []),
    format("   apl(~q, [pairs=Ps, left=Left], R).~n", [PairMaskExpr]),
    format("R = ~q.~n~n", [EvenLeftPairs]),

    section('26. Search results can be mapped symbolically with APL verbs'),
    findall(TripleFD, apl('pythag 20', [module=apl_showcase], TripleFD), Triples),
    apl('parity ?triples', [module=apl_showcase, triples=Triples], TripleParities),
    format("?- findall(T, apl('pythag 20', [module=apl_showcase], T), Ts),~n", []),
    format("   apl('parity ?triples', [module=apl_showcase, triples=Ts], R).~n", []),
    format("Ts = ~q,~nR = ~q.~n~n", [Triples, TripleParities]),

    section('27. The same col/3 style scales to larger row operations'),
    TripleMaskExpr =
        '^hyp 2 col ?triples & ?hyp % 2 e 0 m ?triples',
    apl(TripleMaskExpr,
        [triples=Triples, hyp=_Hyp],
        EvenHypTriples),
    format("?- findall(T, apl('pythag 20', [module=apl_showcase], T), Ts),~n", []),
    format("   apl(~q, [triples=Ts, hyp=Hyp], R).~n", [TripleMaskExpr]),
    format("R = ~q.~n~n", [EvenHypTriples]),

    section('28. Arrays can hold ordinary Prolog terms'),
    show_apl('?xs @ {2 0}', [xs=[foo(1),bar(2),baz(3)]]),

    section('29. Goal-only mode still backtracks like Prolog'),
    findall(X2, apl_goal('(?x = 5) | (?x = 8)', [x=X2]), Xs2),
    show_query("findall(X, apl_goal('(?x = 5) | (?x = 8)', [x=X]), Xs)", Xs2).

title(Text) :-
    format("~`=t ~w ~`=t~72|~n~n", [Text]).

section(Text) :-
    format("~w~n", [Text]),
    format("~`-t~72|~n", []).

show_apl(Expr) :-
    once(apl(Expr, Result)),
    format("?- apl(~q, R).~nR = ~q.~n~n", [Expr, Result]).

show_apl(Expr, Env) :-
    once(apl(Expr, Env, Result)),
    format("?- apl(~q, ~q, R).~nR = ~q.~n~n", [Expr, Env, Result]).

show_query(Query, Result) :-
    format("?- ~s.~n", [Query]),
    format("~q.~n~n", [Result]).
