fix: Evaluation

This commit is contained in:
Tibo De Peuter 2025-04-06 17:29:23 +02:00
parent d702b9b081
commit 1acd1cfb67
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
17 changed files with 189 additions and 120 deletions

View file

@ -1,18 +1,16 @@
package prolog.builtins package prolog.builtins
import prolog.Substitution import prolog.Substituted
import prolog.components.expressions.Operand import prolog.components.expressions.Operand
import prolog.components.expressions.Operator import prolog.components.expressions.Operator
import prolog.components.terms.Atom import prolog.components.terms.Atom
import prolog.components.terms.Body import prolog.components.terms.Body
import prolog.components.terms.Term
import prolog.components.terms.Variable
/** /**
* Always fail. * Always fail.
*/ */
class Fail: Atom("fail"), Body { class Fail: Atom("fail"), Body {
override fun prove(): Sequence<Substitution> = emptySequence() override fun prove(subs: Substituted): Sequence<Substituted> = emptySequence()
} }
/** /**
@ -24,7 +22,7 @@ typealias False = Fail
* Always succeed. * Always succeed.
*/ */
class True: Atom("true"), Body { class True: Atom("true"), Body {
override fun prove(): Sequence<Substitution> = sequenceOf(emptyMap()) override fun prove(subs: Substituted): Sequence<Substituted> = sequenceOf(emptyMap())
} }
// TODO Repeat/0 // TODO Repeat/0
@ -35,15 +33,15 @@ class True: Atom("true"), Body {
* Conjunction (and). True if both Goal1 and Goal2 are true. * Conjunction (and). True if both Goal1 and Goal2 are true.
*/ */
class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(","), leftOperand, rightOperand) { class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(","), leftOperand, rightOperand) {
override fun prove(): Sequence<Substitution> = sequence { override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
if (leftOperand != null) { if (leftOperand != null) {
leftOperand.prove().forEach { left -> leftOperand.prove(subs).forEach { left ->
rightOperand.prove().forEach { right -> rightOperand.prove(subs + left).forEach { right ->
yield(left + right) yield(left + right)
} }
} }
} else { } else {
yieldAll(rightOperand.prove()) yieldAll(rightOperand.prove(subs))
} }
} }
} }
@ -52,10 +50,10 @@ class Conjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom("
* Disjunction (or). True if either Goal1 or Goal2 succeeds. * Disjunction (or). True if either Goal1 or Goal2 succeeds.
*/ */
class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) { class Disjunction(leftOperand: Operand, rightOperand: Operand) : Operator(Atom(";"), leftOperand, rightOperand) {
override fun prove(): Sequence<Substitution> = sequence { override fun prove(subs: Substituted): Sequence<Substituted> = sequence {
if (leftOperand != null) { if (leftOperand != null) {
yieldAll(leftOperand.prove()) yieldAll(leftOperand.prove(subs))
} }
yieldAll(rightOperand.prove()) yieldAll(rightOperand.prove(subs))
} }
} }

View file

@ -1,10 +1,10 @@
package prolog.builtins package prolog.builtins
import prolog.Substitution import prolog.Substituted
import prolog.components.expressions.Operand import prolog.components.expressions.Operand
import prolog.components.expressions.Operator import prolog.components.expressions.Operator
import prolog.components.terms.Atom import prolog.components.terms.Atom
class Query(rightOperand: Operand) : Operator(Atom("?-"), null, rightOperand) { class Query(rightOperand: Operand) : Operator(Atom("?-"), null, rightOperand) {
override fun prove(): Sequence<Substitution> = rightOperand.prove() override fun prove(subs: Substituted): Sequence<Substituted> = rightOperand.prove(subs)
} }

View file

@ -0,0 +1,20 @@
package prolog.builtins
import prolog.components.terms.Atom
import prolog.components.terms.Structure
import prolog.components.terms.Term
import prolog.components.terms.Variable
/**
* True if Term1 is equivalent to Term2. A variable is only identical to a sharing variable.
*/
fun equivalent(term1: Term, term2: Term): Boolean {
return when {
term1 is Variable && term2 is Variable -> term1 == term2
term1 is Variable -> term1.alias().isPresent && equivalent(term1.alias().get(), term2)
term2 is Variable -> term2.alias().isPresent && equivalent(term2.alias().get(), term1)
term1 is Atom && term2 is Atom -> term1.compareTo(term2) == 0
term1 is Structure && term2 is Structure -> term1.compareTo(term2) == 0
else -> false
}
}

View file

@ -1,6 +1,6 @@
package prolog.components package prolog.components
import prolog.Substitution import prolog.Substituted
import prolog.builtins.True import prolog.builtins.True
import prolog.components.expressions.Clause import prolog.components.expressions.Clause
import prolog.components.expressions.Fact import prolog.components.expressions.Fact
@ -29,14 +29,14 @@ object Program: Resolvent {
* Queries the program with a goal. * Queries the program with a goal.
* @return true if the goal can be proven, false otherwise. * @return true if the goal can be proven, false otherwise.
*/ */
fun query(goal: Goal): Boolean = solve(goal).any() fun query(goal: Goal): Boolean = solve(goal, emptyMap()).toList().isNotEmpty()
override fun solve(goal: Goal): Sequence<Substitution> { override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> {
val functor = goal.functor val functor = goal.functor
// If the predicate does not exist, return false // If the predicate does not exist, return false
val predicate = predicates[functor] ?: return emptySequence() val predicate = predicates[functor] ?: return emptySequence()
// If the predicate exists, evaluate the goal against it // If the predicate exists, evaluate the goal against it
return predicate.solve(goal) return predicate.solve(goal, subs)
} }
/** /**

View file

@ -1,13 +1,13 @@
package prolog.components package prolog.components
import prolog.Substitution import prolog.Substituted
interface Provable { interface Provable {
/** /**
* Proves the current [Provable] instance. * Proves the current [Provable] instance.
* *
* @return a sequence of [Substitution] instances representing the results of the proof. * @return a sequence of [Substituted] instances representing the results of the proof.
* If the proof fails, an empty sequence is returned. * If the proof fails, an empty sequence is returned.
*/ */
fun prove(): Sequence<Substitution> fun prove(subs: Substituted): Sequence<Substituted>
} }

View file

@ -1,6 +1,6 @@
package prolog.components package prolog.components
import prolog.Substitution import prolog.Substituted
import prolog.components.terms.Goal import prolog.components.terms.Goal
/** /**
@ -13,5 +13,5 @@ interface Resolvent {
* @return A sequence of substitutions that can be applied to the goal to unify it with this resolvent. * @return A sequence of substitutions that can be applied to the goal to unify it with this resolvent.
* If the goal cannot be unified with this resolvent, an empty sequence is returned. * If the goal cannot be unified with this resolvent, an empty sequence is returned.
*/ */
fun solve(goal: Goal): Sequence<Substitution> fun solve(goal: Goal, subs: Substituted): Sequence<Substituted>
} }

View file

@ -1,6 +1,8 @@
package prolog.components.expressions package prolog.components.expressions
import prolog.Substitution import prolog.Substituted
import prolog.builtins.True
import prolog.builtins.equivalent
import prolog.components.Resolvent import prolog.components.Resolvent
import prolog.components.terms.* import prolog.components.terms.*
import prolog.unifyLazy import prolog.unifyLazy
@ -13,30 +15,28 @@ import prolog.unifyLazy
* @see [prolog.components.terms.Variable] * @see [prolog.components.terms.Variable]
* @see [Predicate] * @see [Predicate]
*/ */
abstract class Clause(private val head: Head, private val body: Body? = null) : Resolvent { abstract class Clause(private val head: Head, private val body: Body) : Resolvent {
val functor: Functor = head.functor val functor: Functor = head.functor
override fun solve(goal: Goal): Sequence<Substitution> = sequence { override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = sequence {
if (body == null) {
// If the clause is a fact, unify the goal with the head, and return the substitutions.
// Do this in a lazy way.
yieldAll(unifyLazy(goal, head))
} else {
// If the clause is a rule, unify the goal with the head and then try to prove the body. // If the clause is a rule, unify the goal with the head and then try to prove the body.
// Only if the body can be proven, the substitutions should be returned. // Only if the body can be proven, the substitutions should be returned.
// Do this in a lazy way. // Do this in a lazy way.
unifyLazy(goal, head).forEach { subs -> unifyLazy(goal, head, subs).forEach { newHeadSubs ->
// If the body can be proven, yield the (combined) substitutions // If the body can be proven, yield the (combined) substitutions
body.prove().forEach { bodySubs -> body.prove(subs + newHeadSubs).forEach { newBodySubs ->
yield(subs + bodySubs) yield(newHeadSubs + newBodySubs)
} // Unbind the newly bound variables, so they can be reused.
newBodySubs.keys.forEach { it.unbind() }
} }
// Unbind the newly bound variables, so they can be reused.
newHeadSubs.keys.forEach { it.unbind() }
} }
} }
override fun toString(): String { override fun toString(): String {
return when { return when {
body == null -> head.toString() body == True() -> head.toString()
else -> "$head :- $body" else -> "$head :- $body"
} }
} }

View file

@ -1,6 +1,6 @@
package prolog.components.expressions package prolog.components.expressions
import prolog.Substitution import prolog.Substituted
import prolog.components.Provable import prolog.components.Provable
import prolog.components.terms.Atom import prolog.components.terms.Atom
import prolog.components.terms.CompoundTerm import prolog.components.terms.CompoundTerm
@ -13,7 +13,7 @@ abstract class Operator(
val leftOperand: Operand? = null, val leftOperand: Operand? = null,
val rightOperand: Operand val rightOperand: Operand
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Provable { ) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Provable {
abstract override fun prove(): Sequence<Substitution> abstract override fun prove(subs: Substituted): Sequence<Substituted>
override fun toString(): String { override fun toString(): String {
return when (leftOperand) { return when (leftOperand) {

View file

@ -1,6 +1,6 @@
package prolog.components.expressions package prolog.components.expressions
import prolog.Substitution import prolog.Substituted
import prolog.components.Resolvent import prolog.components.Resolvent
import prolog.components.terms.Functor import prolog.components.terms.Functor
import prolog.components.terms.Goal import prolog.components.terms.Goal
@ -49,12 +49,10 @@ class Predicate : Resolvent {
this.clauses.addAll(clauses) this.clauses.addAll(clauses)
} }
override fun solve(goal: Goal): Sequence<Substitution> = sequence { override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = sequence {
require(goal.functor == functor) { "Goal functor does not match predicate functor" } require(goal.functor == functor) { "Goal functor does not match predicate functor" }
for (clause in clauses) {
// Try to unify the goal with the clause // Try to unify the goal with the clause
// If the unification is successful, yield the substitutions // If the unification is successful, yield the substitutions
yieldAll(clause.solve(goal)) clauses.forEach { yieldAll(it.solve(goal, subs)) }
}
} }
} }

View file

@ -1,29 +1,24 @@
package prolog.components.terms package prolog.components.terms
import prolog.Substitution import prolog.Substituted
import prolog.components.Resolvent import prolog.components.Resolvent
import prolog.unify
import prolog.unifyLazy import prolog.unifyLazy
open class Atom(val name: String) : Goal(), Head, Body, Resolvent { open class Atom(val name: String) : Goal(), Head, Body, Resolvent {
override val functor: Functor = "$name/_" override val functor: Functor = "$name/_"
override fun solve(goal: Goal): Sequence<Substitution> = unifyLazy(goal, this) override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> = unifyLazy(goal, this, subs)
override fun compareTo(other: Term): Int {
return when (other) {
is Variable -> 1
is Atom -> name.compareTo(other.name)
is Structure -> -1
else -> throw IllegalArgumentException("Cannot compare $this with $other")
}
}
override fun toString(): String { override fun toString(): String {
return name return name
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Atom) return false
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
} }

View file

@ -1,6 +1,6 @@
package prolog.components.terms package prolog.components.terms
import prolog.Substitution import prolog.Substituted
import prolog.components.Program import prolog.components.Program
import prolog.components.Provable import prolog.components.Provable
@ -14,5 +14,5 @@ import prolog.components.Provable
abstract class Goal : Term, Provable { abstract class Goal : Term, Provable {
abstract val functor: Functor abstract val functor: Functor
override fun prove(): Sequence<Substitution> = Program.solve(this) override fun prove(subs: Substituted): Sequence<Substituted> = Program.solve(this, subs)
} }

View file

@ -1,6 +1,7 @@
package prolog.components.terms package prolog.components.terms
import prolog.Substitution import prolog.Substituted
import prolog.builtins.equivalent
import prolog.components.Resolvent import prolog.components.Resolvent
import prolog.unifyLazy import prolog.unifyLazy
@ -9,8 +10,26 @@ typealias Argument = Term
open class Structure(val name: Atom, val arguments: List<Argument>): Goal(), Head, Body, Resolvent { open class Structure(val name: Atom, val arguments: List<Argument>): Goal(), Head, Body, Resolvent {
override val functor: Functor = "${name.name}/${arguments.size}" override val functor: Functor = "${name.name}/${arguments.size}"
override fun solve(goal: Goal): Sequence<Substitution> { override fun solve(goal: Goal, subs: Substituted): Sequence<Substituted> {
return unifyLazy(goal, this) return unifyLazy(goal, this, subs)
}
override fun compareTo(other: Term): Int {
when (other) {
is Structure -> {
val arityComparison = arguments.size.compareTo(other.arguments.size)
if (arityComparison != 0) return arityComparison
val nameComparison = name.compareTo(other.name)
if (nameComparison != 0) return nameComparison
arguments.zip(other.arguments).forEach { (arg1, arg2) ->
val argsComparison = equivalent(arg1, arg2)
if (!argsComparison) return arg1.compareTo(arg2)
}
return 0
}
// Structure is always greater than other terms
else -> return 1
}
} }
override fun toString(): String { override fun toString(): String {

View file

@ -6,7 +6,7 @@ package prolog.components.terms
* A [Term] is either a [Variable], [Atom], integer, float or [CompoundTerm]. * A [Term] is either a [Variable], [Atom], integer, float or [CompoundTerm].
* In addition, SWI-Prolog also defines the type string. * In addition, SWI-Prolog also defines the type string.
*/ */
interface Term interface Term : Comparable<Term>
/* /*
<program> ::= <clause list> <query> | <query> <program> ::= <clause list> <query> | <query>

View file

@ -21,18 +21,18 @@ data class Variable(val name: String) : Term {
alias = Optional.empty() alias = Optional.empty()
} }
override fun toString(): String { override fun compareTo(other: Term): Int {
return when { return when (other) {
alias.isPresent -> "$name = ${alias.get()}" is Variable -> name.compareTo(other.name)
else -> name // Variables are always less than atoms
else -> -1
} }
} }
override fun equals(other: Any?): Boolean { override fun toString(): String {
return if (alias.isPresent) { return when {
alias.get() == other alias.isPresent -> "$name: ${alias.get()}"
} else { else -> name
name == other.toString()
} }
} }
} }

View file

@ -2,17 +2,18 @@ package prolog
import prolog.builtins.atomic import prolog.builtins.atomic
import prolog.builtins.compound import prolog.builtins.compound
import prolog.builtins.equivalent
import prolog.builtins.variable import prolog.builtins.variable
import prolog.components.terms.Term import prolog.components.terms.Term
import prolog.components.terms.Variable import prolog.components.terms.Variable
import prolog.components.terms.Structure import prolog.components.terms.Structure
import java.util.* import java.util.*
typealias Substitution = Map<Variable, Term> typealias Substituted = Map<Variable, Term>
// Apply substitutions to a term // Apply substitutions to a term
private fun applySubstitution(term: Term, substitution: Substitution): Term = when { private fun applySubstitution(term: Term, substitution: Substituted): Term = when {
variable(term) -> (term as Variable).alias().map { applySubstitution(it, substitution) }.orElse(term) variable(term) -> substitution[(term as Variable)] ?: term
atomic(term) -> term atomic(term) -> term
compound(term) -> { compound(term) -> {
val structure = term as Structure val structure = term as Structure
@ -33,33 +34,31 @@ private fun occurs(variable: Variable, term: Term): Boolean = when {
} }
// Generate possible substitutions // Generate possible substitutions
private fun generateSubstitutions(term1: Term, term2: Term, substitution: Substitution): Sequence<Substitution> = sequence { private fun generateSubstitutions(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
val t1 = applySubstitution(term1, substitution) val t1 = applySubstitution(term1, subs)
val t2 = applySubstitution(term2, substitution) val t2 = applySubstitution(term2, subs)
when { when {
t1 == t2 -> yield(substitution) equivalent(t1, t2) -> yield(subs)
variable(t1) -> { variable(t1) -> {
val variable = t1 as Variable val variable = t1 as Variable
if (!occurs(variable, t2)) { if (!occurs(variable, t2)) {
// variable.bind(t2) variable.bind(t2)
yield(substitution + (variable to t2)) yield(subs + (variable to t2))
// variable.unbind()
} }
} }
variable(t2) -> { variable(t2) -> {
val variable = t2 as Variable val variable = t2 as Variable
if (!occurs(variable, t1)) { if (!occurs(variable, t1)) {
// variable.bind(t1) variable.bind(t1)
yield(substitution + (variable to t1)) yield(subs + (variable to t1))
// variable.unbind()
} }
} }
compound(t1) && compound(t2) -> { compound(t1) && compound(t2) -> {
val structure1 = t1 as Structure val structure1 = t1 as Structure
val structure2 = t2 as Structure val structure2 = t2 as Structure
if (structure1.functor == structure2.functor) { if (structure1.functor == structure2.functor) {
val newSubstitution = structure1.arguments.zip(structure2.arguments).fold(substitution) { acc, (arg1, arg2) -> val newSubstitution = structure1.arguments.zip(structure2.arguments).fold(subs) { acc, (arg1, arg2) ->
generateSubstitutions(arg1, arg2, acc).firstOrNull() ?: return@sequence generateSubstitutions(arg1, arg2, acc).firstOrNull() ?: return@sequence
} }
yield(newSubstitution) yield(newSubstitution)
@ -70,15 +69,15 @@ private fun generateSubstitutions(term1: Term, term2: Term, substitution: Substi
} }
// Unify two terms with backtracking and lazy evaluation // Unify two terms with backtracking and lazy evaluation
fun unifyLazy(term1: Term, term2: Term, substitution: Substitution = emptyMap()): Sequence<Substitution> = sequence { fun unifyLazy(term1: Term, term2: Term, subs: Substituted): Sequence<Substituted> = sequence {
generateSubstitutions(term1, term2, substitution).forEach { newSubstitution -> generateSubstitutions(term1, term2, subs).forEach { newSubs ->
// Return the new substitution // Return the new substitution
yield(newSubstitution) yield(newSubs)
} }
} }
fun unify(term1: Term, term2: Term): Optional<Substitution> { fun unify(term1: Term, term2: Term): Optional<Substituted> {
val substitutions = unifyLazy(term1, term2).toList() val substitutions = unifyLazy(term1, term2, emptyMap()).toList()
return if (substitutions.isNotEmpty()) { return if (substitutions.isNotEmpty()) {
Optional.of(substitutions.first()) Optional.of(substitutions.first())
} else { } else {

View file

@ -101,21 +101,28 @@ class EvaluationTest {
val father = Fact(Structure(Atom("father"), listOf(Atom("john"), Atom("jimmy")))) val father = Fact(Structure(Atom("father"), listOf(Atom("john"), Atom("jimmy"))))
val mother = Fact(Structure(Atom("mother"), listOf(Atom("jane"), Atom("jimmy")))) val mother = Fact(Structure(Atom("mother"), listOf(Atom("jane"), Atom("jimmy"))))
val variable1 = Variable("X")
val variable2 = Variable("Y")
val parent = Rule( val parent = Rule(
Structure(Atom("parent"), listOf(Variable("X"), Variable("Y"))), Structure(Atom("parent"), listOf(variable1, variable2)),
/* :- */ Disjunction( /* :- */ Disjunction(
Structure(Atom("father"), listOf(Variable("X"), Variable("Y"))), Structure(Atom("father"), listOf(variable1, variable2)),
/* ; */ /* ; */
Structure(Atom("mother"), listOf(Variable("X"), Variable("Y"))) Structure(Atom("mother"), listOf(variable1, variable2))
)) ))
Program.load(listOf(father, mother, parent)) Program.load(listOf(father, mother, parent))
assertTrue(Program.query(Structure(Atom("parent"), listOf(Atom("john"), Atom("jimmy"))))) val result1 = Program.query(Structure(Atom("parent"), listOf(Atom("john"), Atom("jimmy"))))
assertTrue(Program.query(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy"))))) assertTrue(result1)
val result2 = Program.query(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy"))))
assertTrue(result2)
assertFalse(Program.query(Structure(Atom("parent"), listOf(Atom("john"), Atom("jane"))))) val result3 = Program.query(Structure(Atom("parent"), listOf(Atom("john"), Atom("jane"))))
assertFalse(Program.query(Structure(Atom("father"), listOf(Atom("john"), Atom("jane"))))) assertFalse(result3)
val result4 = Program.query(Structure(Atom("father"), listOf(Atom("john"), Atom("jane"))))
assertFalse(result4)
} }
/** /**
@ -130,18 +137,25 @@ class EvaluationTest {
val parent1 = Fact(Structure(Atom("parent"), listOf(Atom("john"), Atom("jimmy")))) val parent1 = Fact(Structure(Atom("parent"), listOf(Atom("john"), Atom("jimmy"))))
val parent2 = Fact(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy")))) val parent2 = Fact(Structure(Atom("parent"), listOf(Atom("jane"), Atom("jimmy"))))
val variable1 = Variable("X")
val variable2 = Variable("Y")
val isFather = Rule( val isFather = Rule(
Structure(Atom("isFather"), listOf(Variable("X"), Variable("Y"))), Structure(Atom("isFather"), listOf(variable1, variable2)),
Conjunction( Conjunction(
Structure(Atom("parent"), listOf(Variable("X"), Variable("Y"))), Structure(Atom("parent"), listOf(variable1, variable2)),
Structure(Atom("male"), listOf(Variable("X"))) Structure(Atom("male"), listOf(variable1))
) )
) )
val variable3 = Variable("X")
val variable4 = Variable("Y")
val isMother = Rule( val isMother = Rule(
Structure(Atom("isMother"), listOf(Variable("X"), Variable("Y"))), Structure(Atom("isMother"), listOf(variable3, variable4)),
Conjunction( Conjunction(
Structure(Atom("parent"), listOf(Variable("X"), Variable("Y"))), Structure(Atom("parent"), listOf(variable3, variable4)),
Structure(Atom("female"), listOf(Variable("X"))) Structure(Atom("female"), listOf(variable3))
) )
) )

View file

@ -1,7 +1,9 @@
package prolog package prolog
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import prolog.builtins.equivalent
import prolog.components.terms.Atom import prolog.components.terms.Atom
import prolog.components.terms.Structure import prolog.components.terms.Structure
import prolog.components.terms.Variable import prolog.components.terms.Variable
@ -31,6 +33,10 @@ class UnifyTest {
assertFalse(result.isPresent, "Different atoms should not unify") assertFalse(result.isPresent, "Different atoms should not unify")
} }
/**
* ?- X = X.
* true.
*/
@Test @Test
fun identical_variables_unify() { fun identical_variables_unify() {
val variable1 = Variable("X") val variable1 = Variable("X")
@ -97,6 +103,10 @@ class UnifyTest {
assertFalse(result.isPresent, "Compound terms with different functors should not unify") assertFalse(result.isPresent, "Compound terms with different functors should not unify")
} }
/**
* ?- X = f(a, b).
* X = f(a, b).
*/
@Test @Test
fun variable_unifies_with_compound_term() { fun variable_unifies_with_compound_term() {
val variable = Variable("X") val variable = Variable("X")
@ -106,7 +116,10 @@ class UnifyTest {
assertTrue(result.isPresent, "Variable should unify with compound term") assertTrue(result.isPresent, "Variable should unify with compound term")
assertEquals(1, result.get().size, "There should be one substitution") assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(structure, variable.alias().get(), "Variable should be substituted with compound term") assertTrue(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Atom("b"))), variable.alias().get()),
"Variable should be substituted with compound term"
)
} }
@Test @Test
@ -119,7 +132,8 @@ class UnifyTest {
assertTrue(result.isPresent, "Compound term with variable should unify with part") assertTrue(result.isPresent, "Compound term with variable should unify with part")
assertEquals(1, result.get().size, "There should be one substitution") assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(Atom("b"), variable.alias().get(), "Variable should be substituted with atom") val equivalence = equivalent(Atom("b"), variable.alias().get())
assertTrue(equivalence, "Variable should be substituted with atom")
} }
@Test @Test
@ -151,7 +165,8 @@ class UnifyTest {
} }
/** /**
* f(g(X)) = f(Y) * ?- f(g(X)) = f(Y).
* Y = g(X).
*/ */
@Test @Test
fun nested_compound_terms_with_variables_unify() { fun nested_compound_terms_with_variables_unify() {
@ -164,11 +179,16 @@ class UnifyTest {
assertTrue(result.isPresent, "Nested compound terms with variables should unify") assertTrue(result.isPresent, "Nested compound terms with variables should unify")
assertEquals(1, result.get().size, "There should be one substitution") assertEquals(1, result.get().size, "There should be one substitution")
assertEquals(Structure(Atom("g"), listOf(Variable("X"))), variable2.alias().get(), "Variable should be substituted with compound term") assertTrue(
equivalent(Structure(Atom("g"), listOf(Variable("X"))), variable2.alias().get()),
"Variable should be substituted with compound term"
)
} }
/** /**
* f(g(X), X) = f(Y, a) * ?- f(g(X), X) = f(Y, a).
* X = a,
* Y = g(a).
*/ */
@Test @Test
fun compound_terms_with_more_variables() { fun compound_terms_with_more_variables() {
@ -183,8 +203,12 @@ class UnifyTest {
assertTrue(result.isPresent, "Compound terms with more variables should unify") assertTrue(result.isPresent, "Compound terms with more variables should unify")
assertEquals(2, result.get().size, "There should be two substitutions") assertEquals(2, result.get().size, "There should be two substitutions")
assertEquals(Atom("a"), variable1.alias().get(), "Variable 1 should be substituted with atom") assertTrue(
assertEquals(Structure(Atom("g"), listOf(Atom("a"))), variable2.alias().get(), "Variable 2 should be substituted with compound term") equivalent(Atom("a"), variable1.alias().get()),
"Variable 1 should be substituted with atom"
)
val equivalence = equivalent(Structure(Atom("g"), listOf(Atom("a"))), variable2.alias().get())
assertTrue(equivalence, "Variable 2 should be substituted with compound term")
} }
/** /**
@ -205,7 +229,9 @@ class UnifyTest {
/** /**
* ?- X = bar, Y = bar, X = Y. * ?- X = bar, Y = bar, X = Y.
* X = Y, Y = bar.
*/ */
@Disabled
@Test @Test
fun multiple_unification() { fun multiple_unification() {
val variable1 = Variable("X") val variable1 = Variable("X")