Implementing Logic Programming

btmc.substack.com

200 points by sirwhinesalot 2 days ago


sytelus - 15 minutes ago

This is great article but it is ruined because author chose to use substack. I don't know why people keep following herd and endup publishing on substack. If you already don't know, Substack has disallowed ChatGPT to crawl anything so you cannot take AI assistance to work on the content. They got this content for free but they want to gatekeep it for profiting from it. Additionally, substack is intentionally horrible at letting allow nicely formatted pdf or readable versions downloaded to lock out the content even further. No one in their right mind should be using substack.

Folks, really, using GitHub pages with static website generator is all you need. Your content will be nice and truly freely accessible to everyone.

kragen - 2 days ago

I second the recommendation in Sir Whinesalot's post (which I haven't fully read yet) to look at miniKanren and microKanren. I found it extremely educational to port microKanren to OCaml a few years ago, and I think the result is somewhat more comprehensible than the original Scheme, though you'll still probably have to read the paper to understand it: http://canonical.org/~kragen/sw/dev3/mukanren.ml

The most astonishing result of miniKanren is a Scheme interpreter that you can run forwards, backwards, or both. http://webyrd.net/quines/quines.pdf demonstrates using it to generate a Scheme quine, that is, a Scheme program whose output when run is itself ("miniKanren, Live and Untagged: Quine Generation via Relational Interpreters (Programming Pearl)", Byrd, Holk, and Friedman).

§4.4 of SICP http://sarabander.github.io/sicp/html/4_002e4.xhtml also has an implementation of logic programming in Scheme which is extremely approachable.

Unlike the post, I don't think Datalog is the place to look for deep insights about logic programming. Instead, it's the place to look for deep insights about databases.

philzook - 2 days ago

Nice!

I'll note there is a really shallow version of naive datalog I rather like if you're willing to compromise on syntax and nonlinear variable use.

   edge = {(1,2), (2,3)}
   path = set()
   for i in range(10):
       # path(x,y) :- edge(x,y).
       path |= edge
       # path(x,z) :- edge(x,y), path(y,z).
       path |= {(x,z) for x,y in edge for (y1,z) in path if y == y1}

Similarly it's pretty easy to hand write SQL in a style that looks similar and gain a lot of functionality and performance from stock database engines. https://www.philipzucker.com/tiny-sqlite-datalog/

I wrote a small datalog from the Z3 AST to sqlite recently along these lines https://github.com/philzook58/knuckledragger/blob/main/kdrag...

ashton314 - 2 days ago

I did a detailed write-up of implementing miniKanren here:

https://codeberg.org/ashton314/microKanren

By the end of it, I implement a small type checker that, when you run it backwards (by giving the checker a type), it proceeds to enumerate programs that inhabit that type!

xelxebar - 2 days ago

https://github.com/Seeker04/plwm

This window manager implemented in Prolog popped up here recently. It's really cool!

I jumped to it as a new daily driver in the hope that I'd learn some Prolog, and it's been quite the success, actually. The developer is really nice, and he's generously helped me with some basic questions and small PRs.

Definitely recommended. I have a Guix package for it if anyone's interested.

Any reading recommendations for high quality logic programming codebases?

xlii - 2 days ago

Lately I’ve been dabbling with different Prolog implementations and Constraint Handling Rules which led me to CLIPS [0] (in Public Domain, but developed at NASA - sounds neat doesn’t it?)

It’s not very easy to get into, but it’s very fast on rule resolution and being pure C is easy to integrate. I’m trying to get smart code parsing using logic language and this seems promising. I’m also a Lisp nerd so that works for me :)

[0]: https://www.clipsrules.net/

deosjr - 2 days ago

I recently implemented a version of Bret Victor's Dynamicland in the browser using miniKanren, Datalog, and WebAssembly: https://deosjr.github.io/dynamicland/

Knowing how to implement a small logic programming language from scratch really feels like a superpower sometimes.

tracnar - 2 days ago

For logic in Python this project looks pretty neat, it encodes facts as typed objects and rules as functions, then allows you to run the model using a solver like soufflé: https://py-typedlogic.github.io/

I haven't found an excuse to really use it though!

michae2 - 2 days ago

Something I’ve wondered about Datalog is whether integers can be added to the language without losing guarantees about termination of query evaluation. It seems like as soon as we add integers with successor() or strings with concat() then we can potentially create infinite relations. Is there a way to add integers or strings (well, really basic scalar operations on integer or string values) while preserving termination guarantees?

This bit at the end of the article seems to imply it’s possible, maybe with some tricks?

> We could also add support for arithmetic and composite atoms (like lists), which introduce some challenges if we wish to stay “Turing-incomplete”.

scapbi - 2 days ago

This thread was the final push I needed to add logic programming to Mochi https://github.com/mochilang/mochi — a small statically typed scripting language I’m building for agents and real-time data.

I gave OpenAI Codex a single prompt with a sample like:

  fact parent("Alice", "Bob")
  rule grandparent(x, z) :- parent(x, y), parent(y, z)
  let gps = query grandparent(x, z)

And it generated a working Datalog engine in Go with:

  - fact storage + recursive rule support
  - bottom-up fixpoint evaluation
  - unification and `!=` constraints
  - FFI bindings to expose `fact`, `rule`, and `query` to scripts
Full thinking process: https://chatgpt.com/s/cd_684d3e3c59c08191b20c49ad97b66e01

Total implementation was ~250 LOC. Genuinely amazed how effective the LLM was at helping bootstrap a real logic layer in one go.

The PR is here https://github.com/mochilang/mochi/pull/616

sundarurfriend - a day ago

The only real experience I've had with logic programming is via Brachylog[1], and I enjoyed it very much.

It's a golfing language that I used on codegolf.SE, so my experience is probably very different from that of someone who used logic programming for "real" coding. But I found the experience very pleasurable: the fun in codegolfing is largely from bending your mind in unusual ways to approach the problem (and its sub-problems) differently to solve it with terser code. Brachylog added to this by providing its own set of (what to me were) unusual approaches, thus making sure that the process was always fresh and weird enough to be fun.

[1] https://github.com/JCumin/Brachylog/wiki

fracus - 2 days ago

I think it would be really impactful to start with a problem and describe how logic programming solves that problem better than the other paradigms.

jpfr - a day ago

Microkanren et al are nice! But it is becoming sort of a mono-culture where other approaches get ignored.

Before Microkanren, the rite of passage for logic programming was to build a Prolog using Warren's Abstract Machine (WAM).

https://direct.mit.edu/books/monograph/4253/Warren-s-Abstrac...

kriro - 2 days ago

I love Prolog but haven't used it in ages. I should really give it a go again. Whenever I used it my brain needed some adjustment time and then switched to declarative mode. It's a really unique experience and hard to describe. It was also my first contact with immutable data.

Is implementing a Kanren and embedding it as suggested by the author really the recommended approach? Back in the day I used Sicstus mostly but tried to use SWI whenever possible (because I'm a FLOSS guy at heart). I'm asking because we went the opposite direction and used Prolog as the first language and called Java or C when needed (io, GUI). I'd describe the result as a "hot mess".

Random note: "Art of Prolog" and "Craft of Prolog" remain two of my favorite CS books to this day.

I'd be curious what the "state of the art" is these days and would love ve to hear from some folks using Prolog in the trenches.

IamDaedalus - 2 days ago

I was thinking of an idea very similar to this some weeks ago where you define the "rules" that structure your program and I think prolog is the one! I'll looking into getting a taste this weekend

usgroup - 2 days ago

Prolog seems cursed to be forgotten and re-discovered in a never ending cycle.

- 2 days ago
[deleted]
YeGoblynQueenne - 8 hours ago

>> The main issue is that Prolog is not truly declarative; how you write the rules has a major impact on how the interpreter behaves. You might end up with repeated answers for a query or it might enter an infinite loop. Prolog also allows IO, so the order in which things get executed is critical. It’s Turing-complete, for better or worse.

Yeah, sounds like a real show-stopper. Prolog is not 100% declarative, which is really what every programmer wants from a programming language.

Better use a language like C# or Java, or Python, which are 0% declarative. Or try to implement datalog (only not really) so that you can't even use lists because that's what real-word programmers are looking for in a real-world programming language: 100% declarative, 0% real-world uses.

Ah well, at least the author has heard of SLD-Resolution which is more than one can say about the authors of SICP. But they haven't heard of SLG Resolution with tabulation (a.k.a. "tabling") which is a way to execute Prolog programs while preserving the Turing-completeness but without getting stuck in left-recursions. Among other things.

https://www.swi-prolog.org/pldoc/man?section=tabling

Edit:

>> Prolog also allows IO, so the order in which things get executed is critical.

I paid €7 to make this post because I'm on a boat in the middle of the Adriatic sea and my Vodafone contract is AWOL, so listen closely: no, Prolog's IO is not what makes it Turing Complete. That comes from definite logic, i.e. a fragment of the First Order Predicate Calculus (a.k.a. First Order Logic) restricted to formulae in clausal form with at most one positive literal a.k.a Horn clauses, that nevertheless retains the full expressivity of full FOL. Definite logic is semi-decidable and when evaluated by Resolution, as SLD-Resolution (restricted to definite program clauses and Horn goals), it is refutation-complete (and sound, to boot). Meaning, in summary, that any true sentence can be proved true but if a sentence is false then it cannot always be proved false, by SLD-Resolution. So far, so theoretically good.

What Prolog does which is really dangerous and makes the order of execution "critical" is that it allows the programmer to edit the program database i.e. the database of facts and rules, in other words, the program itself, programmatically, i.e from the program itself. This means that not all your program's state is known before the program runs and database operations are completed. That sucks hairy balls but it is also a very useful mechanism that requires experience and understanding of the language, its core concepts, and programming in general, to use right.

But Prolog's Turing completeness has nothing to do with IO, or database ops. That was not worth €7.

vvern - 2 days ago

Some time ago I worked on cockroachdb and I was working on implementing planning for complex online schema changes.

We really wanted a model that could convincingly handle and reasonably schedule arbitrary combinations of schema change statements that are valid in Postgres. Unlike mysql postgres offers transactional schema changes. Unlike Postgres, cockroach strives to implement online schema changes in a protocol inspired by f1 [0]. Also, you want to make sure you can safely roll back (until you’ve reached the point where you know it can’t fail, then only metadata updates are allowed).

The model we came up with was to decompose all things that can possibly change into “elements” [1] and each element had a schedule of state transitions that move the element through a sequence of states from public to absent or vice versa [2]. Each state transitions has operations [3].

Anyway, you end up wanting to define rules that say that certain element states have to be entered before other if the elements are related in some way. Or perhaps some transitions should happen at the same time. To express these rules I created a little datalog-like framework I called rel [4]. This lets you embed in go a rules engine that then you can add indexes to so that you can have sufficiently efficient implementation and know that all your lookups are indexed statically. You write the rules in Go [5]. To be honest it could be more ergonomic.

The rules are written in Go but for testing and visibility they produce a datomic-inspired format [6]. There’s a lot of rules now!

The internal implementation isn’t too far off from the search implementation presented here [7]. Here’s unify [8]. The thing has some indexes and index selection for acceleration. It also has inverted indexes for set containment queries.

It was fun to make a little embedded logic language and to have had a reason to!

0: https://static.googleusercontent.com/media/research.google.c... 1: https://github.com/cockroachdb/cockroach/blob/f48b3438a296aa... 2: https://github.com/cockroachdb/cockroach/blob/f48b3438a296aa... 3: https://github.com/cockroachdb/cockroach/blob/f48b3438a296aa... 4: https://github.com/cockroachdb/cockroach/blob/f48b3438a296aa... 5: https://github.com/cockroachdb/cockroach/blob/f48b3438a296aa... 6: https://github.com/cockroachdb/cockroach/blob/master/pkg/sql... 7: https://github.com/cockroachdb/cockroach/blob/f48b3438a296aa... 8: https://github.com/cockroachdb/cockroach/blob/f48b3438a296aa...

b0a04gl - 2 days ago

i tried writing a tiny logic engine once after reading the SICP chapter and yeah, the syntax part was easy to mimic but the actual backtracking logic hit me harder than expected. what helped was thinking of it less like solving and more like building a lazy search tree. once that clicked, i stopped trying to force control flow and just let the tree expand. one thing i didn’t see many mention handling stateful part or side effects blows up fast. even printing during backtracking messes the whole thing. most tutorials avoid that part completely

sirwhinesalot - 2 days ago

Or more accurately, a super simple Datalog implementation.

cartucho1 - 2 days ago

Not really logic programming, but a while ago I made this, also in Python: https://github.com/ariroffe/logics (mainly for educational purposes). Parts of the implementation kind of reminded me of it.