view prolog/utils.pl @ 2:546bfd3988b0

Added unpolished pldoc documentation.
author samer
date Fri, 13 Jan 2012 15:53:04 +0000
parents 0dd31a8c66bd
children
line wrap: on
line source
% Some general utilities

:- module(utils,[

	% type testing and enumeration
		natural/1		% test or enumerate natural numbers
	,	isfinite/1     % check number is non NaN or Inf
	,	int/1				% test or enumerate integers
	,	in/2

	% mathematical utilities
	,	max/3
	,	min/3

	% list utilities
	,	list_idx1_member/3  % like nth1 but more useful argument order
	,	apply_to_nth1/4
	,	measure/3      % match list lengths
	,  equal_length/2 % match 2 list lengths
	,	getopts/2
	,  rep/3          % make a list of repeats of the same term
	,  cons/3         % list constructror
	,	decons/3       % list deconstructor

	% comma lists
	,	cl_list/2
	,	cl_list_vt/2
	,	cl_length/2
	,	cl_length_vt/2
	,	cl_member/2


	% term manipulation
	,	copy_head/2    % check terms for same head and arity
	,	unify_args/5
	,	reinstatevars/3
	,	reinstatevars/4

	% formatting and parsing
	,	aphrase/2		% like phrase but makes an atom instead of a string
	,	aphrase/3		% like aphrase but takes code list
	,	print_list/1	% writes each element on a new line 
	,	printq_list/1	% as print_list but quotes atom as necessary
	,	print_numbered_list/1
	
	% database management
	,	extensible/1	% declare dynamic multifile predicate
	,	bt_assert/1
	,	bt_retract/1
	,	strict_assert/1
	,	strict_retract/1
	,	browse/2			% browse predicate with unknown arity
	,	current_state/2
	,	set_state/2 

	% file system utilities
	,	dir/2				% directory listing
	,	path_atom/2		% expand paths
	,	expand_path/2	% expand paths

	% operating system 
	,	open_with/2		% apply shell command to a file (SIDE EFFECTS)
	,	open_with/3		% apply shell command to a file with options (SIDE EFFECTS)
	,	shellcmd/2		% apply shell command to arguments
	,	open_/1			% open with 'open' command (Mac OS X).
	, 	fmt_shellcmd/3 % format a shell command with switches and args
	,	hostname/1

	% user interaction
	,	read_char_echo/1 % read one character and echo it immediately
	,	get_key/2		% read and validate keypress
	,	userchk/1		% unary predicate which allows user to force failure
	,	prompt_for_key/3

	% ---- high order stuff ----
	
	% modes of computation
	,	retry/1			% keep trying until success
	,  parallel/1 		% parallel computation using threads
	,	bt_call/2		% Construct backtrackable operation
	,	on_backtracking/1

	% iteration
	,	exhaust/1		% trigger all solutions to a goal (SIDE EFFECTS)
	,	iterate/3		% apply predicate recursively till failure

	% mapping
	,	for_naturals/2 % call predicate with natural numbers N down to 1
	,	mapints/2      % call predicate with integers
	,	mapints/3      
	,	mapargs/2, mapargs/3, mapargs/4
	,	mapargs_x/4, mapargs_x/5, mapargs_x/6
	,	mapargs_xx/6
	,	sfold/4			% structural fold for lists
	,	take/3
	,	drop/3
	,	drop_while/3
	,	take_while/3

	% lambda expressions
	,	mcall/2, mcall/3, mcall/4

	]).

/** <module> General utilities

The predicates in this module can be divided into several groups as listed
below.

---+++ Type testing and enumeration
	*	natural/1		- test or enumerate natural numbers
	*	isfinite/1     - check number is non NaN or Inf
	*	int/1				- test or enumerate integers
	*	in/2

---+++	 mathematical utilities
	*	max/3
	*	min/3

---+++	 list utilities
	*	list_idx1_member/3  % like nth1 but more useful argument order
	*	measure/3      - match list lengths
	*	equal_length/2 - match two list lenghts
	*	getopts/2
	*  rep/3          - make a list of repeats of the same term
	*  cons/3         - make list from head and tail
	*  decons/3       - get head and tail from list

---+++ term manipulation
	*	copy_head/2    - check terms for same head and arity
	*	unify_args/5
	*	reinstatevars/3
	*	reinstatevars/4

---+++ formatting and parsing
	*	aphrase/2		- like phrase but makes an atom instead of a string
	*	aphrase/3		- like aphrase but takes code list
	*	print_list/1	- write/1 each element, one per line
	*	printq_list/1	- as print_list but quotes atom as with writeq/1
	*	print_numbered_list/1 - as print_list/1 but with line numbers
	
---+++ database management
	*	extensible/1	- declare dynamic multifile predicate
	*	bt_assert/1
	*	bt_retract/1
	*	strict_assert/1
	*	strict_retract/1
	*	browse/2			- browse predicate with unknown arity
	*	current_state/2
	*	set_state/2 

---+++ file system utilities
	*	dir/2				- directory listing
	*	path_atom/2		- expand paths
	*	expand_path/2	- expand paths

---+++ operating system 
	*	open_with/2		- apply shell command to a file (SIDE EFFECTS)
	*	open_with/3		- apply shell command to a file with options (SIDE EFFECTS)
	*	shellcmd/2		- apply shell command to arguments
	*	open_/1			- open with 'open' command (Mac OS X).
	* 	fmt_shellcmd/3 - format a shell command with switches and args
	*	hostname/1

---+++ user interaction
	*	read_char_echo/1 - read one character and echo it immediately
	*	get_key/2		- read and validate keypress
	*	userchk/1		- unary predicate which allows user to force failure
	*	prompt_for_key/3 - print message and get keypress from user

---+++ High order stuff
	
---++++ modes of computation
	*	retry/1			- keep trying until success
	*  parallel/1 		- parallel computation using threads
	*	bt_call/2		- Construct backtrackable operation
	,	on_backtracking/1

---++++ iteration
	*	exhaust/1		- trigger all solutions to a goal (SIDE EFFECTS)
	*	iterate/3		- apply predicate recursively till failure

---++++ mapping
	*	for_naturals/2 - call predicate with natural numbers N down to 1
	*	mapints/2      - call predicate with integers
	*	mapints/3      
	*	mapargs/2, mapargs/3, mapargs/4
	*	mapargs_xx/6
	*	sfold/4			- structural fold for lists
	*	take/3
	*	drop/3
	*	drop_while/3
	*	take_while/3

---++++ lambda expressions
	*	mcall/2, mcall/3, mcall/4
 */

:- use_module(library(ops)).

:- meta_predicate
		exhaust(0)
	,	retry(0)
	,	iterate(2,?,?)
	,	drop_while(1,?,?)
	,	take_while(1,?,?)
	,	apply_to_nth1(?,2,?,?)
	,	for_naturals(+,1)
	,	for_ints(+,+,1)
	,	mapints(1,?)
	,	mapints(2,?,?)
	,	mapargs(1,?)
	, 	mapargs(2,?,?)
	,	mapargs(3,?,?,?)
	,	mapargs_x(+,+,1,?)
	, 	mapargs_x(+,+,2,?,?)
	,	mapargs_x(+,+,3,?,?,?)
	,	mapargs_xx(2,?,?,?,?,?)
	,	on_backtracking(0)
	,	bt_call(0,0)
	,	bt_assert(:)
	,	bt_retract(:)
	,	strict_assert(:)
	,	strict_retract(:)
	,	extensible(:)
	,	aphrase(2,?)
	,	aphrase(2,?,?)
	.

:- multifile user:path/2.
:- multifile user:demo/1.
:- dynamic current_state/2.


%% extensible(+P) is det.
%  declares 'extensible' predicates, ie ones that can have new clauses
%  added in other files. Equivalent to dynamic(P), multifile(P).
extensible(P) :- dynamic(P), multifile(P).


%% natural(+N) is semidet.
%% natural(-N:natural) is multi.
%
% Means N is a natural number (includes 0). If N is
% a variable, succeeds an infinite number of times on backtracking,
% returning all natural numbers.
natural(N) :- (var(N) -> between(0,inf,N); integer(N), N>=0).


%% int(+N) is semidet.
%% int(-N:integer) is multi.
%
% Means N is an integer. If N is
% a variable, succeeds an infinite number of times on backtracking,
% returning all integers starting at zero and interleaving positive
% and negative values.
int(N)     :- nonvar(N), integer(N).
int(N)     :- var(N), (N=0; (between(1,inf,M), (N=M; N is -M))).


%% isfinite(+N:number) is semidet.
%
%  Succeeds when N is a finite number.
isfinite(N):- catch(_ is N+0,error(_,_),fail). % !! workable hack


%% in(+X,Set) is semidet.
%% in(-X,Set) is nondet.
%
%  Simple testing and enumration of values in some sets.
%  Set can be
%  * {A,B,_}
%    Explicit list of values.
%  * natural
%    Natural numbers starting from 0.
%  * integer
%    Natural numbers.
%  * real
%    Real (floating point) numbers.
%  * A..B
%    Integer range from A to B inclusive.
%  * A--B
%    Closed interval [A,B].
X in A--\B   :- X in A--(\B).
X in \A--(\B):- !, ch(A<X), ch(X<B).
X in \A--B   :- !, ch(A<X), ch(X=<B).
X in A--(\B) :- !, ch(A=<X), ch(X<B).
X in A--B    :- !, ch(A=<X), ch(X=<B).
X in A..B    :- integer(A), integer(B), between(A,B,X). 
X in {CList} :- member_clist(X,CList).
X in natural :- natural(X). % enumerate!
X in integer :- int(X). % enumerates!
X in real    :- number(X). % same as X :: real


ch(_  =<inf) :- !.
ch(inf=< _ ) :- !, fail.
ch(-inf=< _) :- !.
ch(_ =<(-inf)) :- !, fail.
ch(A=<B) :- !, A=<B.

ch(inf<_  ) :- !, fail.
ch(_  <inf) :- !.
ch(_   <(-inf)) :- !, fail.
ch(-inf<_     ) :- !.
ch(A<B) :- !, A<B.



%% exhaust(:Goal) is det.
%
%  Repeat Goal until failure, then succeed.
exhaust(Q) :- call(Q), fail; true.

%% iterate(+P:pred(A,A), X:A, Y:A) is semidet.
%  apply P recursively to X until failure, then return final value Y.
iterate(P,X,Y) :- call(P,X,Z) -> iterate(P,Z,Y); Y=X.               

%% sfold(Functor,Initial,L:list,Final) is semidet.
% *Structural* fold applied to a term,
% rather than a relational fold using a predicate name.
sfold(_,E,[],E).
sfold(O,E,[X|XX],R) :- R=..[O,X,YY], sfold(O,E,XX,YY).


%% dir( +Pattern, -File) is nondet.
%
% Directory listing for directory matching Pattern. File
% names are returned one by one on backtracking.
dir(Pattern,File) :-
	expand_file_name(Pattern,List),
	member(File,List).


%% path_atom( +Spec:path_expr, -Path:atom) is nondet.
%
%  Expand a 'path expression' into a flat path (an atom).
%  A path expression is defined by:
%  ==
%  path_expr ---> atom            % literal path component name
%               ; path_expr/atom  % child of path_expr
%               ; path_macro      % a previously defined abbr for a path.
%  ==
%  A path_macro is defined using path/2.

path_atom(P,C) :- path(P,C), atom(C).
	
path_atom(PA/B,C) :-
	once((nonvar(C); nonvar(B); nonvar(PA))),
	path_atom(PA,A),
	concat3atoms(A,'/',B,C).


path_atom(Path,Atom) :- path(Path, Def), \+atom(Def), path_atom(Def,Atom).
path_atom(Path,Atom) :-
	nonvar(Path),
	\+path(Path,_),
	Path\=_/_,
	Atom=Path.

concat3atoms(A,B,C,ABC) :-
	nonvar(A), nonvar(B), nonvar(C), !, concat_atom([A,B,C],ABC).

concat3atoms(_,_,_,ABC) :- var(ABC), !, fail.
concat3atoms(A,B,C,ABC) :- nonvar(C), !, atom_concat(AB,C,ABC), atom_concat(A,B,AB). 
concat3atoms(A,B,C,ABC) :- nonvar(A), !, atom_concat(A,BC,ABC), atom_concat(B,C,BC). 
concat3atoms(A,B,C,ABC) :-
	maplist(atom_codes,[B,ABC],[BX,ABCX]),
	append(ABX,CX,ABCX),
	append(AX,BX,ABX),
	maplist(atom_codes,[A,C],[AX,CX]).

%% expand_path( +P:path_expr, -FP:atom) is semidet.
%
%  Expand path_exp including wildcards to fully qualified path
expand_path(P,FP) :- path_atom(P,PP), expand_file_name(PP,[FP]).


%% open_with( +Program:atom, +Thing:path_expr, +Options:list) is semidet.
%% open_with( +Program:atom, +Thing:path_expr) is semidet.
%
%  The only option is bg, which adds "&" to make command execute in background.
open_with(Q,P) :- open_with(Q,P,[]).
open_with(Q,P,Opts) :- 
	expand_path(P,FP), 
	(member(bg,Opts) -> OO=' &'; OO=''),
	sformat(S,'~w ~q~w',[Q,FP,OO]), 
	shell(S).

%% open_( +Thing:path_expr) is semidet.
%  Equivalent to open_with(open,Thing).
open_(P) :- open_with(open,P).

%% shellcmd( +Head:atom, +Args:list(atom)) is det.
%
% Execute a shell command on a given list of arguments
shellcmd(Head,Args) :-
	concat_atom([Head|Args],' ',Cmd),
	shell(Cmd,_Status).

%% fmt_shellcmd( +Prog:atom, +Args:list(shellarg), -Cmd) is det.
% make a shell command string.
fmt_shellcmd(Prog,Args,Cmd) :-
	phrase(utils:shellcmd(l_(Args)),FArgs),
	concat_atom([Prog|FArgs],' ',Cmd).

shellcmd(l_([]))    --> !, [].
shellcmd(l_([H|T])) --> !, shellcmd(H), shellcmd(l_(T)).
shellcmd(s(N,V))  --> !, shellcmd(s(N)), shellcmd(V).
shellcmd(q(X)) --> !, { concat_atom(['"',X,'"'],A) }, [A].
shellcmd(s(N)) --> !, { 
	(atom_codes(N,[_]) -> F='-' ; F='--'),
	atom_concat(F,N,A) }, [A].
shellcmd(l(X)) --> [X].



%% read_char_echo( -C:atom) is det.
%
%  Read a single character from the current input,
%  echo it to the output.
read_char_echo(C) :-
	get_single_char(Code), 
	put_code(Code), flush_output,
	char_code(C,Code). 



%% set_state( +Key, +Value) is det.
%
% Maintains a mutable global set of Key-Value pairs, sets the value
% associated with Key to Value.
set_state(Flag,Value) :-
	ground(Flag),
	retractall(current_state(Flag,_)),
	assert(current_state(Flag,Value)).


%% current_state( -Key, -Value) is nondet.
%% current_state( +Key, -Value) is semidet.
%
%  Lookup the value associated with Key, or enumerate all the
%  key value pairs.



%% parallel( +List:list(query)) is semidet.
%
% Use this by giving a list of queries of the form
%    [Vars2:Goal, Vars2:Goal2, ...]
% where Vars is the term that each thread must return
% when it has finished computing its Goal. The 
% parallel predicate finishes when all the threads
% have finished, and should result in all the Vars
% being bound appropriately. 

parallel(Queries) :-
	maplist(async,Queries,Collecters),
	maplist(call,Collecters).

% these are used to initiate and wait for each
% computation thread.
async_collect(Id,X:_) :- thread_join(Id,exited(X)).
async(X:Goal,utils:async_collect(Id,X:_)) :-
	thread_create((Goal,thread_exit(X)),Id,[]).

%% browse( +PredSpec, -Goal) is nondet.
%
%  PredSpec is a term like (PredicateName/Arity). Goal
%  is unified with solutions of PredicateName/Arity.
browse(P/A,Goal) :-
	current_predicate(P/A),
	length(L,A),
	Goal=..[P|L],
	call(Goal).



%% aphrase(P:phrase(code), -A:atom, +S:list(code)) is nondet.
%% aphrase(P:phrase(code), +A:atom, -S:list(code)) is nondet.
%% aphrase(P:phrase(code), -A:atom, -S:list(code)) is nondet.
%% aphrase(P:phrase(code), -A:atom) is nondet.
%
%  Generate or parse an atom using given DCG phrase P.
%  aphrase(P,A) is equivalent to aphrase(P,A,_).
aphrase(X,A) :- aphrase(X,A,_). 
aphrase(X,A,S) :- 
	(	ground(A) 
	->	atom_codes(A,S), phrase(X,S)
	;	phrase(X,S), atom_codes(A,S)).


%% print_list( +L:list) is det.
%
%  Print a list, one item per line.
print_list([]) :- writeln('~'), nl.
print_list([H|T]) :- print(H), nl, print_list(T).

%% printq_list( +L:list) is det.
%
%  Print a list, one item per line, as with writeq/1.
printq_list([]) :- writeln('~'), nl.
printq_list([H|T]) :- writeq(H), nl, printq_list(T).

%% print_numbered_list( +L:list) is det.
%
%  Print a list with numbered lines.
print_numbered_list(L) :- 
	length(L,Max), 
	number_codes(Max,MC),
	length(MC,Width),
	print_num_list(Width,1,L).

print_num_list(_,_,[]) :- nl.
print_num_list(Width,N,[H|T]) :- succ(N,M),
	copy_term(H,H1),
	numbervars(H1,0,_),
	number_codes(N,NC), " "=[Pad],
	padleft(Pad,Width,NC,PNC),
	format('~s. ~q\n',[PNC,H1]), 
	print_num_list(Width,M,T).

padleft(_,W,In,In) :- length(In,W).
padleft(P,W,In,[P|Out]) :- succ(V,W), padleft(P,V,In,Out).




%% get_key( +Valid:list(char), -C:char) is det.
%
%  Get and validate a key press from the user. The character
%  must be one of the ones listed in Valid, otherwise, an
%  error message is printed and the user prompted again.
get_key(Valid,C) :-
	read_char_echo(D), nl,
	(	member(D,Valid) -> C=D
	;	D='\n' -> get_key(Valid,C) % this improves interaction with acme
	;	format('Unknown command "~q"; valid keys are ~q.\n', [D,Valid]),
		write('Command? '),
		get_key(Valid,C)).


%% userchk(T) is semidet.
%
%  Write T and ask this user if it is ok. User presses y or n.
%  userchk succeeds if if the keypress was y and fails if it was n.
userchk(T) :- prompt_for_key(T,[y,n],y).


%% prompt_for_key( +Msg:atom, +Keys:list(char), -Key:char) is semidet.
%
%  Prompt user for a keypress. Prompt message is Msg, and valid keys are
%  listed in Keys. 
prompt_for_key(Msg,Keys,Key) :- format('~p ~q? ',[Msg,Keys]), get_key(Keys,Key).

% ------------------- TERM MANIPULATION ------------------------------


%% copy_head(+T1,-T2) is det.
%% copy_head(+T1,+T2) is semidet.
%
% true if T1 and T2 have the same head and arity
copy_head(T1,T2) :- functor(T1,N,A), functor(T2,N,A).



%% reinstatevars( F:atom, V:list, Eh, What) is nondet.
%% reinstatevars( V:list, Eh, What) is nondet.
%
% Reverse of numbervars. Each '$VAR'(N) subterm of X is replaced
% with the Nth element of V, which can be uninstantiated on entry
% reinstatevars/4 uses an arbitrary functor F instead of $VAR.

reinstatevars(V,'$VAR'(N),Y) :- !, nth0(N,V,Y).
reinstatevars(_,X,Y) :- atomic(X), !, Y=X.
reinstatevars(V,X,Y) :- mapargs(reinstatevars(V),X,Y).

reinstatevars(F,V,T,Y) :- functor(T,F,1), !, arg(1,T,N), nth0(N,V,Y).
reinstatevars(_,_,X,Y) :- atomic(X), !, Y=X.
reinstatevars(F,V,X,Y) :- mapargs(reinstatevars(F,V),X,Y).


%% unify_args( Src, SrcIndex, Dest, DestIndex, Num) is det.
%
% this unifies N consecutive arguments of Src and Dest starting
% from SI and DI in each term respectively
unify_args(_,_,_,_,0).
unify_args(Src,SI,Dest,DI,N) :-
	arg(SI,Src,X), arg(DI,Dest,X), !,
	succ(SI,SI2), succ(DI,DI2), succ(N2,N),
	unify_args(Src,SI2,Dest,DI2,N2).


% ---------------------- LIST UTILITIES -----------------------------


%member_clist(_,Z) :- var(Z), !, fail.
member_clist(A,A) :- A\=(_,_).
member_clist(A,(A,_)).
member_clist(A,(_,B)) :- member_clist(A,B).



%% measure(Ruler,In,Out) is det.
% true if Out is the same length as Ruler but matches In as far as possible
measure([],_I,[]).
measure([_|R],[],[_|O]) :- measure(R,[],O).
measure([_|R],[X|I],[X|O]) :- measure(R,I,O).

%% equal_length( ?In, ?Out) is nondet.
%  equal_length( +L1:list, -L2:list) is det.
%  equal_length( -L1:list, +L2:list) is det.
%
%  True if L1 and L2 are the same length.
equal_length([],[]).
equal_length([_|T1],[_|T2]) :- equal_length(T1,T2).

%split_at(0,T,I-I,T).	
%split_at(N,[H|T],[H|I1]-Z,T1) :- succ(M,N), split_at(M,T,I1-Z,T1).	

%split_at2(0,T,I-I,T).	
%split_at2(N,[H|T],[H|I1]-Z,T1) :- split_at2(M,T,I1-Z,T1), succ(M,N).	

%% max(+X:number, +Y:number, -Z:number) is det.
%
%  Unify Z with the larger of X and Y. Legal values are
%  any numerical value or inf or -inf.
max(_,inf,inf) :- !.
max(inf,_,inf) :- !.
max(X,-inf,X)  :- !.
max(-inf,X,X)  :- !.
max(X,Y,Z)     :- X<Y -> Z=Y; Z=X.

%% max(+X:number, +Y:number, -Z:number) is det.
%
%  Unify Z with the larger of X and Y. Legal values are
%  any numerical value or inf or -inf.
min(_,-inf,-inf) :- !.
min(-inf,_,-inf) :- !.
min(X,inf,X)  :- !.
min(inf,X,X)  :- !.
min(X,Y,Z)     :- X<Y -> Z=X; Z=Y.

%% list_idx1_member( +L:list(A), +N:natural, ?X:A) is semidet.
%% list_idx1_member( ?L:list(A), ?N:natural, ?X:A) is nondet.
%
%  Equivalent to nth1(N,L,X).
list_idx1_member(L,I,X) :- nth1(I,L,X).


%% getopts( +Opts:list(option), ?Spec:list(optspec))  is det.
%
%  Get value from option list. 
%  ==
%  option(A) ---> term(A).
%  optspec   ---> option(A)/A.
%  ==
getopts(OptsIn,Spec) :- maplist(getopt(OptsIn),Spec).
getopt(OptsIn,Option/Default) :- option(Option,OptsIn,Default).

%% cons( ?Head:A, ?Tail:list(A), ?List:list(A)) is det.
%
%  List constructor.
cons(H,T,[H|T]).

%% decons( ?Head:A, ?List:list(A), ?Tail:list(A)) is det.
%
%  List deconstructor.
decons(H,[H|T],T).

% ---------------------- MAPPING, HIGH ORDER STUFF ---------------------

%% for_naturals(+N:natural, P:pred(natural)) is nondet.
% apply predicate to each natural number from 1 to N (backwards)
for_naturals(0,_).
for_naturals(N,P) :- succ(M,N), call(P,N), for_naturals(M,P). 

%% mapints( +P:pred(integer,A), +R:intrange, -X:list) is nondet.
%% mapints( +P:pred(integer), +R:intrange) is nondet.
%
% Mapping predicates over lists of integers. Range is like M..N.
% mapints/3 maps 2 argument predicate over implicit list of
% integers M..N and explicit list of values X.
mapints(_,M..N) :- N<M, !.
mapints(P,M..N) :- call(P,M), plus(M,1,L), mapints(P,L..N).

mapints(_,M..N,[])    :- N<M, !.
mapints(P,M..N,[X|T]) :- call(P,M,X), plus(M,1,L), mapints(P,L..N,T).

%% rep( +N:natural, ?X:A, -L:list(A)) is det.
%% rep( -N:natural, ?X:A, -L:list(A)) is multi.
% Make a list consisting of N repeats of the same term. If called
% with N unbount, creates progressively longer and longer lists
% on backtracking.
rep(0,_,[]).
rep(N,A,[A|X]) :- 
	(	nonvar(N) 
	-> succ(M,N), rep(M,A,X)
	; rep(M,A,X), succ(M,N)
	).

%% mapargs( P:pred(A,B,C), T1:tuple(F,A), T2:tuple(F,B), T3:tuple(F,C)) is nondet.
%% mapargs( P:pred(A,B), T1:tuple(F,A), T2:tuple(F,B)) is nondet.
%% mapargs( P:pred(A), T1:term) is nondet.
%
% Map predicate over to args of a term preserving head.
% A tuple(F,A) is a term with head functor F and any number of arguments
% of type A, ie
% ==
% tuple(F,A) ---> F ; F(A) ; F(A,A) ; F(A,A,A) ; .. .
% ==

mapargs(P,T1) :- 
	functor(T1,_,N), 
	mapargs_x(1,N,P,T1).

mapargs(P,T1,T2) :-
	(	nonvar(T1)  
	-> functor(T1,F,N), functor(T2,F,N)
	;	functor(T2,F,N), functor(T1,F,N)),
	mapargs_x(1,N,P,T1,T2).

mapargs(P,T1,T2,T3) :-
	functor(T1,F,N),
	functor(T2,F,N),
	functor(T3,F,N),
	mapargs_x(1,N,P,T1,T2,T3).

mapargs_x(I,N,P,T1) :-
	(	I>N -> true
	;  arg(I,T1,X1),
		call(P,X1),
		succ(I,J), mapargs_x(J,N,P,T1)).

mapargs_x(I,N,P,T1,T2) :-
	(	I>N -> true
	;  arg(I,T1,X1),
		arg(I,T2,X2),
		call(P,X1,X2),
		succ(I,J), mapargs_x(J,N,P,T1,T2)).

mapargs_x(I,N,P,T1,T2,T3) :-
	(	I>N -> true
	;  arg(I,T1,X1),
		arg(I,T2,X2),
		arg(I,T3,X3),
		call(P,X1,X2,X3),
		succ(I,J), mapargs_x(J,N,P,T1,T2,T3)).

%% drop( +N:natural, +In:list(A), -Out:list(A)) is det.
drop(0,T,T).
drop(N,[_|T],V) :- succ(M,N), drop(M,T,V).


%% take( +N:natural, +In:list(A), -Out:list(A)) is det.
take(N,T,X) :- length(X,N), append(X,_,T).


%% drop_while( +P:pred(A), +In:list(A), -Out:list(A)) is det.
%
%  Remove all elements from head of In that are accepted by P
%  and return the remained in Out.
drop_while(P,[X|T],V) :- call(P,X) -> drop_while(P,T,V); V=[X|T].


%% take_while( +P:pred(A), +In:list(A), -Out:list(A)) is det.
%
%  Remove all elements from head of In that are accepted by P
%  and return them in Out.
take_while(P,[X|T],O) :- call(P,X) -> O=[X|V], take_while(P,T,V); O=[].



%% retry( :Goal) is det.
%
%  Keep retrying Goal until it succeeds. Only makes sense if Goal
%  has side effects. Might be nonterminating.
retry(G) :- once((repeat,G)).

%% apply_to_nth1( N:natural, Op:pred(A,A), +In:list(A), +Out:list(A)) is nondet.
%
%  Apply predicate Op to the N th element of list In and unify Out with the result.
%apply_to_nth1(N,Op,Old,Init) :- 
%	(	nonvar(N) 
%	-> succ(M,N), split_at(M,Old,Init-[Y|Tail],[X|Tail])
%	;	split_at2(M,Old,Init-[Y|Tail],[X|Tail]), succ(M,N) 
%	),
%	call(Op,X,Y).

apply_to_nth1(1,P,[X|XX],[Y|XX]) :- call(P,X,Y).
apply_to_nth1(N,P,[X|X1],[X|Y1]) :- nonvar(N), !, N>1, succ(M,N), apply_to_nth1(M,P,X1,Y1).
apply_to_nth1(N,P,[X|X1],[X|Y1]) :- var(N), !, apply_to_nth1(M,P,X1,Y1), succ(M,N).


%% mapargs_xx( +P:pred(A,B), +Src:term(_,A), +SrcIndex:natural, +Dest:term(_,B), +DestIndex:natural, +N:natural) is nondet.
%
%  Maps predicate P over N consecutive arguments of Src and Dest. Starts
%  at SrcIndex th argument of Src and DestIndex th argument of Dest.
mapargs_xx(_,_,_,_,_,0).
mapargs_xx(Pred,Src,SI,Dest,DI,N) :-
	arg(SI,Src,SX), arg(DI,Dest,DX), call(Pred,SX,DX), !,
	succ(SI,SI2), succ(DI,DI2), succ(N2,N),
	mapargs_xx(Pred,Src,SI2,Dest,DI2,N2).


%% mcall(P:pred(A), X:A) is nondet.
%% mcall(P:pred(A,B), X:A, Y:B) is nondet.
%% mcall(P:pred(A,B,C), X:A, Y:B, Z:C) is nondet.
%% mcall(P:pred(A,B,C,D), X:A, Y:B, Z:C, W:D) is nondet.
%
%  Like call/N but P can additionally be a lambda expression in one of several
%  forms:
%  * Tuple :- Body
%  If functor(Tuple,\,N), Body is executed after unifying tuple arguments
%  with arguments to mcall, eg =mcall(\(X):-member(X,[a,b,c]),Y)= is equivalent
%  to member(Y,[a,b,c]), or =mcall(\(X,Y):-nth1(X,[a,b,c],Y),2,c)=.
%  * Tuple
%  Equivalent to Tuple:-true.

mcall(P,A) :- mc(P,\(A),Q), Q.
mcall(P,A,B) :- mc(P,\(A,B),Q), Q.
mcall(P,A,B,C) :- mc(P,\(A,B,C),Q), Q.
mcall(P,A,B,C,D) :- mc(P,\(A,B,C,D),Q), Q.

mc(Tuple:-Body,Params,Goal) :- !, copy_term(Tuple/Body,Params/Goal).
mc(Tuple,Params,true)       :- functor(Tuple,\,_), !, copy_term(Tuple,Params).
mc(P,Params,apply(P,Args))  :- Params=..[\|Args].


%% on_backtracking( :Goal) is det.
%
%  The first time this is called, it succeeds and does nothing.
%  On backtracking, Goal is called and then a failure is generated.

on_backtracking(_).
on_backtracking(P) :- P, !, fail.


%% bt_call( :Do, :Undo) is nondet.
%
%  Creates a backtrackable operation from a non-backtrackable Do
%  operation and a corresponding operation to undo it. Do can
%  be non-deterministic, in which case bt_call(Do,Undo) will also
%  have multiple solutions. Undo is called inside once/1.
%
%  bt_call/2 is implemented both as a predicate and as a goal
%  expansion (see goal_expansion/2).
bt_call(Do,Undo)  :- Do, (true; once(Undo), fail).

user:goal_expansion( bt_call(Do,Undo), (Do, (true; once(Undo), fail))).



/* Might include these at some point

% apply lambda term to another term
app(X\\F,Y,G) :- !, copy_term(X\\F,Y\\G).
app(T,A,Z)   :- addargs(T,[A],Z). 
app(T,A,B,Z) :- addargs(T,[A,B],Z).
app(T,A,B,C,Z) :- addargs(T,[A,B,C],Z).

applist(F,N,A,Z) :- length(Z,N), maplist(app(F),A,Z).
applist(F,N,A,B,Z) :- length(Z,N), maplist(app(F),A,B,Z).
applist(F,N,A,B,C,Z) :- length(Z,N), maplist(app(F),A,B,C,Z).

*/


% ------------------ DATABASE ------------------------------

%% bt_assert(Clause) is det.
%  Backtrackable assert.
bt_assert(H)      :- bt_call(assert(H),retract(H)).

%% bt_retract(Clause) is det.
%  Backtrackable retract.
bt_retract(H)     :- bt_call(retract(H), assert(H)).

%% strict_assert(Fact) is semidet.
%
%  Asserts fact only if it is not already true. Fails
%  if fact is already provable. Retracts fact on backtracking.
strict_assert(H)  :- \+call(H), bt_call(assert(H),retract(H)).


%% strict_retract(Fact) is semidet.
%
%  Retracts fact only if it is currently in the database. Fails
%  if fact is not provable. Reasserts fact on backtracking.
strict_retract(H) :- call(H), bt_call(retract(H), assert(H)).


% when loaded, this sets the hostname/1 predicate.
:- dynamic hostname/1.

%% hostname( -A:atom) is det.
%
%  Unifies A with the computer's hostname. This is set when the 
%  module is loaded by calling the system command 'hostname -s'.

% init_hostname is det - read hostname from UNIX command hostname.
init_hostname :-
	setup_call_cleanup(
		open(pipe('hostname -s'),read,SID), 
		(read_line_to_codes(SID,C), atom_codes(H,C), retractall(hostname(_)), assert(hostname(H))),
		close(SID)).

:- (	hostname(H)
	-> format('% hostname already set to ~w\n',[H])
	;  init_hostname, hostname(H), format('% hostname set to ~w\n',[H])
	).

% Comma lists
% ie, lists built up using (,) as a pairing functor
% Note, these functor lists do NOT have a nil element - the
% last item in the list is the 2nd argument to the final
% functor term, which can therefore be a term headed by any
% other functor. Eg:
% (1,(2,3))    <-> [1,2,3]
% (1,(2,(3+4)) <-> [1,2,(3+4)]

%% cl_list( +CL:clist(A), -L:list(A)) is det.
%% cl_list( -CL:clist(A), +L:list(A)) is det.
%
%  Convert between comma lists and ordinary lists
cl_list((A,B),[A|BL]) :- cl_list(B,BL). 
cl_list(A,[A]) :- A\=(_,_). 

%% cl_length( +L:clist, -N:natural) is det.
%% cl_length( -L:clist, +N:natural) is det.
%
%  Length of a comma-list.
cl_length((_,B),N) :- cl_length(B,M), succ(M,N).
cl_length(X,1)     :- X\=(_,_).


%% cl_list_vt( +CL:clist(A), -L:list(A)) is det.
%% cl_list_vt( -CL:clist(A), +L:list(A)) is det.
%
%  Convert between comma lists (with open tails) and ordinary lists.
cl_list_vt(FL,[FL])   :- var(FL), !.
cl_list_vt(FL,[A|BL]) :- FL = (A,B), cl_list_vt(B,BL). 
cl_list_vt(A,[A])     :- A\=(_,_). 


%% cl_length_vt( +L:clist, -N:natural) is det.
%% cl_length_vt( -L:clist, +N:natural) is det.
%
%  Length of a comma-list with possible variable tail.
%  This version handles lists where the last element is variable (counts as 1)
cl_length_vt(FL,1) :- var(FL), !.
cl_length_vt(FL,N) :- FL=(_,B), cl_length_vt(B,M), succ(M,N).
cl_length_vt(FL,1) :- FL\=(_,_).

%% cl_member(-X, +L:clist) is nondet.
%  List membership for comma lists.
cl_member(X,(X,_)).	
cl_member(X,(_,T)) :- cl_member(X,T).	
cl_member(X,X)     :- X\=(_,_).