diff --git a/examples/basics/backtracking.pl b/examples/basics/backtracking.pl index 167f037..820bd4f 100644 --- a/examples/basics/backtracking.pl +++ b/examples/basics/backtracking.pl @@ -4,6 +4,6 @@ leq(s(X), s(Y)) :- leq(X, Y). :- initialization(main). main :- - leq(X, s(s(s(0)))), - write(X), nl, + leq(A, s(s(s(0)))), + write(A), nl, fail. diff --git a/src/prolog/ast/logic/Clause.kt b/src/prolog/ast/logic/Clause.kt index 4e97a14..9955539 100644 --- a/src/prolog/ast/logic/Clause.kt +++ b/src/prolog/ast/logic/Clause.kt @@ -27,30 +27,31 @@ abstract class Clause(var head: Head, var body: Body) : Term, Resolvent { // Only if the body can be proven, the substitutions should be returned. // Do this in a lazy way. - val (headEnd, headRenaming) = numbervars(head, Program.variableRenamingStart, subs) + // Since we are only interested in substitutions in the goal (as opposed to the head of this clause), + // we can use variable renaming and filter out the substitutions that are not in the goal. + val (headEnd, headRenaming) = numbervars(head, Program.variableRenamingStart, emptyMap()) Program.variableRenamingStart = headEnd - val renamedHead = applySubstitution(head, subs + headRenaming) - val renamedBody = applySubstitution(body, subs + headRenaming) as Body - - unifyLazy(goal, renamedHead, subs).forEach { headAnswer -> - headAnswer.map { headSubs -> - val subsNotInGoal = headSubs.filterNot { occurs(it.key as Variable, goal, emptyMap()) } - renamedBody.satisfy(subsNotInGoal).forEach { bodyAnswer -> + val renamedHead = applySubstitution(head, headRenaming) + val simplifiedGoal = applySubstitution(goal, subs) + unifyLazy(simplifiedGoal, renamedHead, emptyMap()).forEach { headAnswer -> + headAnswer.map { headAndGoalSubs -> + // Filter the substitutions to only include those that map from head to goal + val headToGoalSubs = headAndGoalSubs.filterKeys { it in headRenaming.values } + val goalToHeadSubs = headAndGoalSubs.filterKeys { occurs(it as Variable, simplifiedGoal) } + val newSubs = headRenaming + headToGoalSubs + body.satisfy(newSubs).forEach { bodyAnswer -> bodyAnswer.fold( - // If the body can be proven, yield the (combined) substitutions onSuccess = { bodySubs -> - var result = headSubs + bodySubs - result = result - .mapValues { applySubstitution(it.value, result) } - .filterKeys { it !is AnonymousVariable && occurs(it as Variable, goal, emptyMap()) } + // If the body can be proven, yield the (combined) substitutions + var result = goalToHeadSubs.mapValues { applySubstitution(it.value, bodySubs) } yield(Result.success(result)) }, onFailure = { error -> if (error is AppliedCut) { // Find single solution and return immediately if (error.subs != null) { - yield(Result.failure(AppliedCut(headSubs + error.subs))) + yield(Result.failure(AppliedCut(headAndGoalSubs + error.subs))) } else { yield(Result.failure(AppliedCut())) } diff --git a/src/prolog/logic/unification.kt b/src/prolog/logic/unification.kt index 6dd033f..50560e0 100644 --- a/src/prolog/logic/unification.kt +++ b/src/prolog/logic/unification.kt @@ -41,7 +41,7 @@ fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when } // Check if a variable occurs in a term -fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean = when { +fun occurs(variable: Variable, term: Term, subs: Substitutions = emptyMap()): Boolean = when { variable(term, subs) -> term == variable atomic(term, subs) -> false compound(term, subs) -> {