Hornet: An embedded DSL for Logic Programming in Python.
Project description
Hornet
Horn clauses via Expression Trees — a Prolog-like embedded DSL for Python ≥ 3.13.
Hornet lets you write logic programs directly in Python. Instead of parsing Prolog strings, it hijacks Python's operator overloading and __call__ syntax to build expression trees, which a resolution engine then solves via unification and backtracking.
Installation
pip install hornet
Requires Python 3.13+. Dependencies: toolz, immutables.
Core Concepts
Terms
Hornet's term algebra mirrors Prolog's:
| Hornet | Prolog equivalent |
|---|---|
Variable('X') / symbols.X |
X (logic variable) |
Atom('foo') / symbols.foo |
foo (atom) |
symbols.foo(X, Y) |
foo(X, Y) (compound term) |
[1, 2, 3] / promote([1,2,3]) |
[1,2,3] (list) |
[H | T] via BitOr |
[H|T] (cons cell) |
Import symbols dynamically from hornet.symbols. Names starting with uppercase become Variables; lowercase become Atoms; _ is the anonymous wildcard.
from hornet.symbols import X, Y, parent, mortal, human
Facts and Rules
Facts and rules are built with .when():
db.tell(
parent('socrates', 'sophroniscus'), # fact
human('socrates'), # fact
mortal(X).when(human(X)), # rule: mortal(X) :- human(X).
)
Queries
from hornet import database
from hornet.symbols import X, mortal
db = database()
db.tell(human('socrates'))
db.tell(mortal(X).when(human(X)))
for subst in db.ask(mortal(X)):
print(subst[X]) # → socrates
db.ask() returns an iterable of substitutions. Each substitution maps variables to their bound values.
Built-in Predicates
Hornet ships a standard library of predicates pre-loaded in every database:
| Predicate | Description |
|---|---|
equal(X, Y) |
Unification (X = Y) |
unequal(X, Y) |
Negation of unification |
let(R, Expr) |
Arithmetic evaluation (R is Expr) |
arithmetic_equal(X, Y) |
Arithmetic equality |
smaller(A, B) / greater(A, B) |
Numeric comparison |
call(G) |
Call a goal term |
once(G) |
Call G, commit to first solution |
findall(O, G, L) |
Collect all solutions |
member(X, L) |
List membership |
append(A, B, C) |
List concatenation |
reverse(L, R) |
List reversal |
select(X, L, R) |
Select element from list |
length(L, N) |
List length |
maplist(G, L) |
Apply goal to each list element |
is_var, nonvar, is_atom, is_int, … |
Type checks |
write, writeln, nl |
I/O |
cut |
Prolog cut (!) |
fail |
Always fails |
true |
Always succeeds |
repeat |
Succeeds infinitely |
throw(E) |
Raise an exception |
ifelse(T, Y, N) |
Soft-cut conditional |
phrase(G, L) |
DCG query |
Arithmetic
Arithmetic expressions are built using Python's operators on symbolic terms and evaluated lazily by let:
from hornet.symbols import X, Y, R, let, arithmetic_equal
# R is X * Y + 1
db.ask(let(R, X * Y + 1))
# supported: + - * / // % ** ~ & | ^ << >>
Definite Clause Grammars (DCGs)
Hornet supports DCG notation via DCG() and DCGs():
from hornet import DCGs, database
from hornet.symbols import S, NP, VP, noun, verb, det, phrase
db = database()
db.tell(*DCGs(
S.when(NP, VP),
NP.when(det, noun),
VP.when(verb),
det.when(['the']),
noun.when(['cat']),
verb.when(['sleeps']),
))
for subst in db.ask(phrase(S, ['the', 'cat', 'sleeps'])):
print('parsed!')
DCG rules are automatically expanded to difference lists. The inline(goal) escape hatch lets you embed regular Prolog goals inside a DCG body.
Extending with Python
Register native Python predicates using the @predicate decorator. This is how ifelse/3 is implemented internally:
from hornet import database, predicate
from hornet.clauses import Database, Subst
from hornet.combinators import Step, if_then_else
from hornet.clauses import resolve
from hornet.symbols import T, Y, N
db = database()
@db.tell
@predicate(ifelse(T, Y, N))
def _(db: Database, subst: Subst) -> Step[Database]:
return if_then_else(
resolve(subst[T]),
resolve(subst[Y]),
resolve(subst[N]),
)(db, subst.map)
Architecture
Hornet is built on two main layers:
Term algebra (hornet.terms): Python expressions construct expression trees rather than computing values. Operator overloading (+, *, |, **, …) and __call__ produce nested Symbolic structures — Functor, Atom, Variable, Cons, Operator subclasses — which represent both data and goals. promote() lifts Python primitives (integers, strings, lists) into this algebra transparently.
Resolution engine (hornet.combinators): A triple-barrelled continuation monad drives search. Every goal is a function (ctx, subst) → Step, where a Step takes three continuations — success (emit a substitution and continue), failure (backtrack), and prune (implement cut). The combinators then, choice, prunable, neg, and if_then_else compose goals; trampoline() drives the whole thing iteratively to avoid stack overflow.
Examples
The examples/ directory includes:
append.py— list splitting via backtrackingqueens.py— N-queens constraint solverfizzbuzz.py— FizzBuzz via DCGssymdiff.py— symbolic differentiation and simplificationparsing.py— natural language parsing with a German grammarturing.py— a Turing machine interpreterhanoi.py— Towers of Hanoi with Turtle graphics
License
MIT. See LICENSE.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hornet_dsl-0.4.0.tar.gz.
File metadata
- Download URL: hornet_dsl-0.4.0.tar.gz
- Upload date:
- Size: 28.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.26.6 CPython/3.12.11 Linux/6.18.12-329.current
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc2b5e81a3ee16ea3b8feb91fee5ea5a65d3f303bddfa4f3890d219a4042e843
|
|
| MD5 |
0ef5775f9979102a1d4fa90a7b6e4eb5
|
|
| BLAKE2b-256 |
56f6718708f3511b87be88338523222bea161e04832c4810dee1ae5b1b8acce7
|
File details
Details for the file hornet_dsl-0.4.0-py3-none-any.whl.
File metadata
- Download URL: hornet_dsl-0.4.0-py3-none-any.whl
- Upload date:
- Size: 21.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.26.6 CPython/3.12.11 Linux/6.18.12-329.current
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd6f5022ab775e276000acfcc51d10d718f4620c538e5002813a89291aac0285
|
|
| MD5 |
47a62ac3d5367eef87b1ad5a5e54ed11
|
|
| BLAKE2b-256 |
7819a7d4c1056cd8fcbff1fbbf6c9aaa5db376b24aa228ebc2d53dd7e033df30
|