% 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(X).
isfunc(F).

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) | Theta], T).
apply(var(X), [for(S, Y) | Theta], XTheta) :-
	X \= Y, apply(var(X), Theta, XTheta).
apply(app(F, Ts), Theta, app(F, TsTheta)) :-
	applys(Ts, Theta, TsTheta).
applys([], Theta, []).
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
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(F, Ts)) :-
	not_occurs(X, Ts).
not_occurs(X, []).
not_occurs(X, [T | Ts]) :- not_occur(X, T), not_occurs(X, Ts).

/*
% sample queries:

% first should fail due to occurs-check
unify(app(plus, [app(z, []), var(n), var(n)]),
      app(plus, [app(z, []), app(s, [var(m)]), var(m)]),
      Theta).

% second one should succeed (see lecture notes)
unify(app(plus, [app(s, [var(m1)]), var(n1), app(s, [var(p1)])]),
      app(plus, [app(s, [app(z, [])]), app(s, [app(s, [app(z, [])])]), var(p)]),
      Theta).

% exponential example for n = 3
% there are 8 occs of "a" in subst for x0
unify(app(g, [var(x0), var(x1), var(x2), var(x3)]),
      app(g, [app(f, [var(x1), var(x1)]), app(f, [var(x2), var(x2)]),
              app(f, [var(x3), var(x3)]), app(a, [])]),
      Theta).

*/
