From a575d242e21d1aa27b26971e7232bea6fd19fc91 Mon Sep 17 00:00:00 2001 From: Brian Bentow Date: Sun, 2 Aug 2026 13:24:40 -0700 Subject: [PATCH] builtin: add :ne, inequality over two evaluated constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mangle has :lt/:le/:gt/:ge but no not-equals, so every inequality must be written as negation over a domain the program derives first. That is a recurring authoring and review cost for a common construct. Adds :ne at the four registration points :lt uses — symbol, arg modes, decision case, and relation type. The relation type is the non-obvious one: without it the predicate parses and reaches evaluation, then fails type inference with 'cannot find assignment that works for premise :ne(A,B)', which reads like a broken implementation. Verified: differs(A,B) :- colour(A), colour(B), :ne(A,B) over three colours yields the six ordered pairs of distinct values. --- builtin/builtin.go | 6 ++++++ symbols/symbols.go | 3 +++ 2 files changed, 9 insertions(+) diff --git a/builtin/builtin.go b/builtin/builtin.go index 93622fb..d462b68 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -36,6 +36,7 @@ var ( symbols.Contains: {ast.ArgModeInput, ast.ArgModeInput}, symbols.Filter: {ast.ArgModeInput}, symbols.Lt: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.Ne: {ast.ArgModeInput, ast.ArgModeInput}, symbols.Le: {ast.ArgModeInput, ast.ArgModeInput}, symbols.Gt: {ast.ArgModeInput, ast.ArgModeInput}, symbols.Ge: {ast.ArgModeInput, ast.ArgModeInput}, @@ -271,6 +272,11 @@ func Decide(atom ast.Atom, subst *unionfind.UnionFind) (bool, []*unionfind.Union } return false, nil, nil + case symbols.Ne.Symbol: + if len(atom.Args) != 2 { + return false, nil, fmt.Errorf("wrong number of arguments for built-in predicate ':ne': %v", atom.Args) + } + return !atom.Args[0].Equals(atom.Args[1]), []*unionfind.UnionFind{subst}, nil case symbols.Lt.Symbol: if len(atom.Args) != 2 { return false, nil, fmt.Errorf("wrong number of arguments for built-in predicate '<': %v", atom.Args) diff --git a/symbols/symbols.go b/symbols/symbols.go index 6151350..393f0f9 100644 --- a/symbols/symbols.go +++ b/symbols/symbols.go @@ -42,6 +42,8 @@ var ( // Lt is the less-than relation on numbers. Lt = ast.PredicateSym{":lt", 2} + // Ne is inequality over any two evaluated constants. + Ne = ast.PredicateSym{":ne", 2} // Le is the less-than-or-equal relation on numbers. Le = ast.PredicateSym{":le", 2} @@ -369,6 +371,7 @@ var ( Filter: NewRelType(BoolType()), // TODO: support float64 Lt: NewRelType(ast.NumberBound, ast.NumberBound), + Ne: NewRelType(ast.AnyBound, ast.AnyBound), Le: NewRelType(ast.NumberBound, ast.NumberBound), Gt: NewRelType(ast.NumberBound, ast.NumberBound), Ge: NewRelType(ast.NumberBound, ast.NumberBound),