From 76bbd1e13e0fa2883184e7ed9041e0d34b0c742b Mon Sep 17 00:00:00 2001 From: Felix Dilke Date: Wed, 28 Jan 2026 17:40:40 +0000 Subject: [PATCH] correctly handling duplicate head entries --- .../src/Datalog/NaiveDatabase.hs | 2 +- .../test/Test/Datalog/NaiveDatabaseSpec.hs | 62 ++++++++++++------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/haskell-experiments/src/Datalog/NaiveDatabase.hs b/haskell-experiments/src/Datalog/NaiveDatabase.hs index 9c720fd..63ae74f 100644 --- a/haskell-experiments/src/Datalog/NaiveDatabase.hs +++ b/haskell-experiments/src/Datalog/NaiveDatabase.hs @@ -120,7 +120,7 @@ withFactsAndRules facts = foldr (addRule . extractRule) (withFacts facts) , _db = NaiveDatabase relationMap' constants' } where - variableNames = extractVariableNames terms + variableNames = nub $ extractVariableNames terms headTermToElement :: Term -> RuleElement headTermToElement (Var name) = RuleElementVariable $ lookupVariable name variableNames diff --git a/haskell-experiments/test/Test/Datalog/NaiveDatabaseSpec.hs b/haskell-experiments/test/Test/Datalog/NaiveDatabaseSpec.hs index 7909679..22d2b17 100644 --- a/haskell-experiments/test/Test/Datalog/NaiveDatabaseSpec.hs +++ b/haskell-experiments/test/Test/Datalog/NaiveDatabaseSpec.hs @@ -25,11 +25,10 @@ spec :: Spec spec = do describe "NaiveDatabase operations" do it "can ingest facts into relations & a universe" $ do - let db = - NaiveDatabase.withFacts - [ "parent(\"alice\", \"bob\")." - , "parent(\"bob\", \"carol\")." - ] + let db = NaiveDatabase.withFacts + [ "parent(\"alice\", \"bob\")." + , "parent(\"bob\", \"carol\")." + ] constants db `shouldBe` Set.fromList (Sym <$> ["alice", "bob", "carol"]) relations db @@ -40,15 +39,14 @@ spec = do ) ] it "can ingest facts and rules" do - let db = - NaiveDatabase.withFactsAndRules - [ "parent(\"alice\", \"bob\")." - , "parent(\"bob\", \"carol\")." - ] - [ - "ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y)." - , "ancestor(X,Y) :- parent(X,Y)." - ] + let db = NaiveDatabase.withFactsAndRules + [ "parent(\"alice\", \"bob\")." + , "parent(\"bob\", \"carol\")." + ] + [ + "ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y)." + , "ancestor(X,Y) :- parent(X,Y)." + ] parentRelation = Relation { _name = "parent" @@ -111,19 +109,39 @@ spec = do , _rules = [ancestorRule] } relations db - `shouldBe` Map.fromList - [ ("ancestor", ancestorRelation) - ] + `shouldBe` Map.singleton "ancestor" ancestorRelation constants db `shouldBe` Set.fromList (Sym <$> ["patriarch"]) - it "can do basic queries" do + it "can ingest facts and rules with duplicate head entries" do let db = - NaiveDatabase.withFacts - [ "parent(\"alice\", \"bob\")." - , "parent(\"bob\", \"carol\")." - ] + NaiveDatabase.withFactsAndRules + [] + ["equivalent(Q,Q) :- ."] + equivalentRule = + RelationRule + { headVariables = ["Q"] + , bodyElements = [] + } + equivalentRelation = + Relation + { _arity = 2 + , _name = "equivalent" + , _tuples = Set.empty + , _rules = [equivalentRule] + } + relations db + `shouldBe` Map.singleton "equivalent" equivalentRelation + + constants db + `shouldBe` Set.empty + + it "can do basic queries" do + let db = NaiveDatabase.withFacts + [ "parent(\"alice\", \"bob\")." + , "parent(\"bob\", \"carol\")." + ] query db "?- parent(alice,X)." `shouldBe` "#NYI" -- ideally, 'bob' (<<$>>) :: (Functor f1, Functor f2) => (a -> b) -> f1 (f2 a) -> f1 (f2 b)