Skip to main content

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-dsl

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:

from hornet import database, predicate, unit
from hornet.clauses import Database, Subst
from hornet.combinators import Step
from hornet.symbols import X

db = database()

@db.tell
@predicate(X)
def _(db: Database, subst: Subst) -> Step[Database]:
    val = subst[X]
    print(f'native hook: {val}')
    return unit(db, subst.map)

Architecture

Hornet is built on three layered abstractions:

Tail-call elimination (hornet.tailcalls): A trampoline/thunk system prevents RecursionError on deep recursion. Functions decorated with @tailcall return deferred frames; trampoline() drives them iteratively.

State monad (hornet.states): Used internally during clause compilation to thread fresh-variable environments without mutation. Exposed via with_state, get_state, set_state.

Triple-barrelled continuation monad (hornet.combinators): The resolution engine carries three continuations — success (emit a substitution), failure (backtrack), and prune (implement cut). Goals are functions (ctx, subst) → Step, and the combinators then, choice, prunable, neg, if_then_else compose them.


Examples

The examples/ directory includes:

  • append.py — list splitting via backtracking
  • queens.py — N-queens constraint solver
  • fizzbuzz.py — FizzBuzz via DCGs
  • symdiff.py — symbolic differentiation and simplification
  • parsing.py — natural language parsing with a German grammar
  • turing.py — a Turing machine interpreter
  • hanoi.py — Towers of Hanoi with Turtle graphics

License

MIT. See LICENSE.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hornet_dsl-0.3.1a1.tar.gz (28.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hornet_dsl-0.3.1a1-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file hornet_dsl-0.3.1a1.tar.gz.

File metadata

  • Download URL: hornet_dsl-0.3.1a1.tar.gz
  • Upload date:
  • Size: 28.6 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

Hashes for hornet_dsl-0.3.1a1.tar.gz
Algorithm Hash digest
SHA256 7de0e1d60bd2b4b5382de3106b88d3c138410a34fdd38284791d962ce351181b
MD5 74672aab86befa0ef1013da16fdab5ec
BLAKE2b-256 5e4582bd60b3cae3be71295e3c44c6aaa9d7a0c9950f29527df79edd22bc096b

See more details on using hashes here.

File details

Details for the file hornet_dsl-0.3.1a1-py3-none-any.whl.

File metadata

  • Download URL: hornet_dsl-0.3.1a1-py3-none-any.whl
  • Upload date:
  • Size: 21.4 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

Hashes for hornet_dsl-0.3.1a1-py3-none-any.whl
Algorithm Hash digest
SHA256 e89ebfa852cf243600e8593785bd53f64de547a6aee22647639d22fe85c124ba
MD5 a8d0151e526cc919008ce0076429d5ef
BLAKE2b-256 32407379c24ab540027d31f0836ad016716c3c50d880b622e6def758313ef15b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page