5
1
Fork
You've already forked spindle-rust
2

Expose structural predicate identity with PredicateKey (functor + arity) #33

Open
opened 2026年07月13日 13:35:44 +02:00 by hugooconnor · 0 comments

Problem

Spindle represents predicate arguments structurally, but does not expose a reusable public identity for a predicate independently of a concrete literal.

Consumers currently have several imperfect choices:

  • use only Literal::name(), conflating predicates with different arities;
  • use LiteralId, which intentionally ignores predicate arguments and arity;
  • use canonical SPL text, which identifies a complete literal rather than its predicate;
  • reconstruct predicate identity independently.

For example, these should not have the same predicate identity:

(ci-green m1) ; ci-green/1
(ci-green m1 attempt-2) ; ci-green/2
ci-green ; ci-green/0

This matters for vocabulary introspection, rule indexing, listener discovery, diagnostics, and tools that need to distinguish a predicate from a ground proposition.

Spindle already uses name and arity together internally in places such as the grounding fact index. Making that concept explicit would provide one canonical implementation for
downstream consumers.

Proposal

Introduce a public structural predicate key:

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]pubstruct PredicateKey{functor: SymbolId,arity: u16,}

Provide constructors and accessors along these lines:

implPredicateKey{pubfn new(functor: SymbolId,arity: usize)-> Result<Self,PredicateKeyError>;pubfn functor_id(self)-> SymbolId;pubfn functor(self)-> &'static str;pubfn arity(self)-> usize;}implLiteral{pubfn predicate_key(&self)-> PredicateKey;}

Body literals should expose the same identity where applicable:

implBodyLogicLiteral{pubfn predicate_key(&self)-> PredicateKey;}

PredicateKey should represent only predicate identity:

  • include functor and arity;
  • exclude argument values;
  • exclude negation;
  • exclude modality;
  • exclude temporal bounds.

Those properties belong to literal occurrences and matching semantics, not predicate identity.

Optional follow-up: observed literal shapes

A separate LiteralShape abstraction may be useful for introspection:

pubstruct LiteralShape{pubpredicate: PredicateKey,pubargs: Vec<TermKind>,}pubenum TermKind{Symbol,Integer,Decimal,Float,Variable,}

This could support diagnostics such as primitive argument-type drift. It should not be required for the initial PredicateKey work.

It may also be preferable to normalize integer, decimal, and float to a common Number kind in compatibility-oriented views because Spindle supports cross-type numeric equality.

LiteralShape would describe observed primitive term kinds only. It would not introduce semantic sorts such as TaskId or AgentId; predicate signatures and nominal types would require
a separate design.

Benefits

  • Gives consumers a structural alternative to parsing literal names.
  • Prevents accidental conflation of p/0, p/1, and p/2.
  • Provides a stable key for predicate-level maps and indexes.
  • Allows rule heads, bodies, and facts to share predicate identity.
  • Improves diagnostics such as "expected ci-green/1, received ci-green/2."
  • Creates a foundation for future predicate signatures without adding a type system now.

Suggested scope

Initial implementation:

  • add PredicateKey;
  • expose it from Literal and BodyLogicLiteral;
  • add formatting such as Display rendering ci-green/1;
  • test equality and separation across functors and arities;
  • document explicitly that negation, mode, temporal data, and argument values are excluded.

Possible later work:

  • reuse PredicateKey in internal indexes;
  • expose it through contract/JSON types where useful;
  • add an optional LiteralShape;
  • consider declared predicate signatures separately.

Open questions

  1. Should arity use u16, u32, or usize internally?
  2. Should PredicateKey store InternedLiteralName rather than raw SymbolId?
  3. Should the body-literal API cover only logical body literals, excluding arithmetic constraints?
  4. Should Display use functor/arity, or should that remain a consumer convention?
## Problem Spindle represents predicate arguments structurally, but does not expose a reusable public identity for a predicate independently of a concrete literal. Consumers currently have several imperfect choices: - use only `Literal::name()`, conflating predicates with different arities; - use `LiteralId`, which intentionally ignores predicate arguments and arity; - use canonical SPL text, which identifies a complete literal rather than its predicate; - reconstruct predicate identity independently. For example, these should not have the same predicate identity: ```spl (ci-green m1) ; ci-green/1 (ci-green m1 attempt-2) ; ci-green/2 ci-green ; ci-green/0 ``` This matters for vocabulary introspection, rule indexing, listener discovery, diagnostics, and tools that need to distinguish a predicate from a ground proposition. Spindle already uses name and arity together internally in places such as the grounding fact index. Making that concept explicit would provide one canonical implementation for downstream consumers. ## Proposal Introduce a public structural predicate key: ```rust #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct PredicateKey { functor: SymbolId, arity: u16, } ``` Provide constructors and accessors along these lines: ```rust impl PredicateKey { pub fn new(functor: SymbolId, arity: usize) -> Result<Self, PredicateKeyError>; pub fn functor_id(self) -> SymbolId; pub fn functor(self) -> &'static str; pub fn arity(self) -> usize; } impl Literal { pub fn predicate_key(&self) -> PredicateKey; } ``` Body literals should expose the same identity where applicable: ```rust impl BodyLogicLiteral { pub fn predicate_key(&self) -> PredicateKey; } ``` PredicateKey should represent only predicate identity: - include functor and arity; - exclude argument values; - exclude negation; - exclude modality; - exclude temporal bounds. Those properties belong to literal occurrences and matching semantics, not predicate identity. ## Optional follow-up: observed literal shapes A separate LiteralShape abstraction may be useful for introspection: ```rust pub struct LiteralShape { pub predicate: PredicateKey, pub args: Vec<TermKind>, } pub enum TermKind { Symbol, Integer, Decimal, Float, Variable, } ``` This could support diagnostics such as primitive argument-type drift. It should not be required for the initial PredicateKey work. It may also be preferable to normalize integer, decimal, and float to a common Number kind in compatibility-oriented views because Spindle supports cross-type numeric equality. LiteralShape would describe observed primitive term kinds only. It would not introduce semantic sorts such as TaskId or AgentId; predicate signatures and nominal types would require a separate design. ## Benefits - Gives consumers a structural alternative to parsing literal names. - Prevents accidental conflation of p/0, p/1, and p/2. - Provides a stable key for predicate-level maps and indexes. - Allows rule heads, bodies, and facts to share predicate identity. - Improves diagnostics such as "expected ci-green/1, received ci-green/2." - Creates a foundation for future predicate signatures without adding a type system now. ## Suggested scope Initial implementation: - add PredicateKey; - expose it from Literal and BodyLogicLiteral; - add formatting such as Display rendering ci-green/1; - test equality and separation across functors and arities; - document explicitly that negation, mode, temporal data, and argument values are excluded. Possible later work: - reuse PredicateKey in internal indexes; - expose it through contract/JSON types where useful; - add an optional LiteralShape; - consider declared predicate signatures separately. ## Open questions 1. Should arity use u16, u32, or usize internally? 2. Should PredicateKey store InternedLiteralName rather than raw SymbolId? 3. Should the body-literal API cover only logical body literals, excluding arithmetic constraints? 4. Should Display use functor/arity, or should that remain a consumer convention?
Sign in to join this conversation.
No Branch/Tag specified
main
spec/predicate-model-vocabulary
feat/lean-verification-all
hence/plan-IMPL-016
hence/plan-IMPL-015
hence/plan-IMPL-020
hence/plan-IMPL-018
fix/fold-determinism
feat/dates-times
feat/extensions-stratification-documentation
feat/stratification-and-aggregation
feat/builtins-as-extensions
feat/extension-interface
fix/bug-hunt-fixes
feat/lean-verification
v0.3.0
v0.2.0
v0.1.1
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
anuna/spindle-rust#33
Reference in a new issue
anuna/spindle-rust
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?