% Unification on an explicit representation of terms
% Purposely not using built-in meta-programming facilities of Prolog

% type predicates for terms, sequences of terms, and substitutions
% do not restrict terms that can represent variables or
% function symbols
isvar(_).
isfunc(_).

term(app(F, Ts)) :- isfunc(F), terms(Ts).
term(var(X)) :- isvar(X).
terms([]).
terms([T | Ts]) :- term(T), terms(Ts).

% for substitutions, do not enforce additional assumptions
subst([]).
subst([for(T, X) | Theta]) :- term(T), isvar(X), subst(Theta).

% apply(t, theta, t') if t' = t theta
apply(var(X), [], var(X)).
apply(var(X), [for(T, X) | _], T).
apply(var(X), [for(_, Y) | Theta], XTheta) :-
	X \= Y, apply(var(X), Theta, XTheta).
apply(app(F, Ts), Theta, app(F, TsTheta)) :-
	applys(Ts, Theta, TsTheta).
applys([], _, []).
applys([T | Ts], Theta, [TTheta | TsTheta]) :-
	apply(T, Theta, TTheta),
	applys(Ts, Theta, TsTheta).

% compose(sigma, theta, sigma') if sigma' = sigma theta
compose([], Theta, Theta).
compose([for(T,X) | Sigma], Theta, [for(TTheta,X) | SigmaTheta]) :-
	apply(T, Theta, TTheta),
	compose(Sigma, Theta, SigmaTheta).

% unify(t, s, theta) if t == s | theta
% unifys(ts, ss, theta) if ts == ss | theta
unify(var(X), var(X), []).
unify(var(X), var(Y), [for(var(Y),X)]) :- X \= Y.
unify(var(X), app(F, Ts), [for(app(F, Ts), X)]) :-
	not_occur(X, app(F, Ts)).
unify(app(F, Ts), var(X), [for(app(F, Ts), X)]) :-
	not_occur(X, app(F, Ts)).
unify(app(F, Ts), app(F, Ss), Theta) :-
	unifys(Ts, Ss, Theta).
unifys([], [], []).
unifys([T | Ts], [S | Ss], Theta1Theta2) :-
	unify(T, S, Theta1),
	applys(Ts, Theta1, Ts1),
	applys(Ss, Theta1, Ss1),
	unifys(Ts1, Ss1, Theta2),
	compose(Theta1, Theta2, Theta1Theta2).

% not_occur(X, T) if x not in FV(T)
not_occur(X, var(Y)) :- X \= Y.
not_occur(X, app(_, Ts)) :-
	not_occurs(X, Ts).
not_occurs(_, []).
not_occurs(X, [T | Ts]) :- not_occur(X, T), not_occurs(X, Ts).
