HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
John-Nagle
nqthm
Common Lisp2016–2026
John-Nagle / nqthm

nqthm

nqthm - the original Boyer-Moore theorem prover, from 1992

2Distinct people
20Mentions
0Explicit recommenders
61GitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

nqthm - the original Boyer-Moore theorem prover, from 1992

GitHub

Where it is used

No documented product use has been verified yet.

What people are saying

No recommendation phrase was found in the indexed comments.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

No other repository is repeatedly linked in the same discussions.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 5:04:57 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year20 dated mentions

Observed mentions: 2016-08-07 04:41:56 UTC to 2026-10-14 02:32:10 UTC.

0123452016-01-01 00:00:00 UTC to 2017-01-01 00:00:00 UTC (exclusive): 4 mentions; 1 submissions, 3 comments20162017-01-01 00:00:00 UTC to 2018-01-01 00:00:00 UTC (exclusive): 5 mentions; 0 submissions, 5 comments2018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments20212022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 3 mentions; 0 submissions, 3 comments2025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2026-01-01 00:00:00 UTC to 2027-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments. Incomplete calendar period.2026*

* An incomplete calendar period is marked with an outlined bar. Its count is not directly comparable with a full period.

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20162016-01-01 00:00:00 UTC2017-01-01 00:00:00 UTC413
20172017-01-01 00:00:00 UTC2018-01-01 00:00:00 UTC505
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC000
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC101
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC101
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC101
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC101
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC101
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC303
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC101
2026 (partial)2026-01-01 00:00:00 UTC2027-01-01 00:00:00 UTC202

What Hacker News says

20 sources
That takes me back to an early 1980s incident where I was criticized for an early computer-generated proof. This was when I was building a program verifier at an aerospace company. We had the first SAT solver, the Oppen-Nelson simplifier, and the first constructive theorem prover, the Boyer-Moore prover.[1] I wanted to show that the four axioms of arrays, which the simplifier assumed to be true, were valid in Boyer-Moore theory. Boyer and Moore had warned me of the soundness risks of adding axioms to their prover. So I tried to prove them from basic Boyer-Moore theory, which is close to the Peano axioms. Boyer-Moore theory is LISP-like in form. You start with (ZERO) and (ADD1 X). So two is represented as (ADD1 (ADD1 (ZERO))). It's all lists of tuples at the bottom. Equality is strict. (EQUAL A B) is true only if the representations of bot
Read the rest
h A and B are identical. This creates a problem. An array can be represented as a list of (index, value), but for EQUAL to work, the list has to be ordered by subscript and each subscript unique. So storing into an array is represented as insertion into an ordered list. That generates a lot of special cases. Accessing an array is a linear list search by recursion: (DEFN selecta (A I) (IF (EQUAL (array-elt-subscript A) I) (array-elt-value A) (IF (EQUAL (array-prev A) (empty-array)) (UNDEFINED) (selecta (array-prev A) I)))) That's around line 3276 in [2]. The other definitions of array operations are similarly simple. Then we get to proving the classic four axioms of arrays. Around 3000 lines later, the classic four "axioms" of arrays have been proved. Much grinding through special cases. It's a neat result, but bulky. So I submitted a paper to JACM. Reviewer comments included "I hate to see something this complicated at the foundations of computer science", and just "This is unnecessary." People were really upset by all that case analysis. The theoretical problem was that I didn't have set theory. I could have built up set theory first, before array theory, then used set theory to prove array theory. That probably would have made mathematicians happier. Or not. Set theory is traditionally axiomatic, but it can be built up from constructive Boyer-Moore tuple theory if desired. I didn't argue with the reviewers. I probably could have found a more elegant path to this result, by creating intermediate lemmas, rather than letting the prover grind through the cases on automatic. But I wasn't going for a PhD. I had real work to do at an aerospace company. So, I've been beaten on by the academic community for a bulky machine proof. Today, nobody would be bothered by this approach. It's only a few thousand lines, and a human can follow it. You can be too early. (You can still run this prover today. I converted it to GNU Common LISP a decade ago. It takes about one second on a modern machine to go from the Peano-like axioms to array theory. It took 45 minutes on the VAX 11/780 I was using back in the early 1980s.) [1] https://github.com/John-Nagle/nqthm [2] https://github.com/John-Nagle/pasv/blob/master/src/work/temp...
In The Four Colour Theorem: The first time machines mogged mathematicians
I haven't seen the Lipton/Perlis/De Millo paper in years. I was around for that argument. Which really dates me. Those guys were pushing for mutation analysis.[1] That's a test for the test suite - you make some random change to the program and see if the test suite catches it. Fuzzing is related to that concept. It's taken way too long for verification to catch on. Here's where I was almost 50 years ago.[2] Part of the problem is that most of the interest came from people in love with the formalism. The notations used by most researchers were terrible, as is pointed out in the Lipton/Perlis/De Millo paper. You want a notation that matches the programming language. We had the basic architecture back then - use a SAT solver on the easy stuff, and something with some AI capability on the hard stuff. We had the Oppen-Nelson simplifier, the
Read the rest
first SAT solver, for the easy stuff. We had the Boyer-Moore prover for the hard stuff. It's Good Old Fashioned AI, and very good for the late 1970s. The SAT solver knocks off over 90% of the verification conditions. Then you want verification notation that creates hard but abstract problems for the AI solver. Like writing two asserts in a row, with the hard problem being to prove the second one from the first. We didn't have enough compute back then. It took about 45 minutes on a VAX 11/780 for the Boyer-Moore prover to build up number theory from something similar to the Peano axioms. Now it takes about a second. I ported the Boyer-Moore prover to GNU Common LISP a few years ago, just to see it live again.[3] With LLMs to do the grunt work, this is a lot less labor-intensive. And it's really needed to keep LLM garbage under control. Given a concrete goal against which to optimize, LLM coding is much more effective. Formal specifications are still hard to write, but there are many important areas of software for which the specification is simple but an efficient implementation is hard. File systems. Databases. Networking. Some kinds of control systems. Stuff that really needs to work right. [1] https://en.wikipedia.org/wiki/Mutation_testing [2] https://www.animats.com/papers/verifier/verifiermanual.pdf [3] https://github.com/John-Nagle/nqthm
In The Case Against Formal Verification, 50 Years Later
This is very like Boyer-Moore theory[1], which builds up mathematics from the Peano axiom level. Boyer and Moore wrote an automated theorem prover that goes with this theory. I have a working copy for GNU Common LISP at [2]. "It is perhaps easiest to think of our program much as one would think of a reasonably good mathematics student: given the axioms of Peano, he could hardly be expected to prove (much less discover) the prime factorization theorem. However, he could cope quite well if given the axioms of Peano and a list of theorems to prove (e.g., “prove that addition is commutative,” . . . “prove that multiplication distributes over addition,” . . . “prove that the result returned by the GCD function divides both of its arguments,” . . . “prove that if the products over two sequences of primes are identical, then the two sequences ar
Read the rest
e permutations of one another”)." - Boyer and Moore [1] https://www.cs.utexas.edu/~boyer/acl.pdf [2] https://github.com/John-Nagle/nqthm/tree/master
In Peano arithmetic is enough, because Peano arithmetic encodes computation
Here's the Boyer-Moore theorem prover[1] from around 1980 doing that.[2] The proofs are fully automatic but you have to ask it to prove the theorems in an order so that the theorems you need for each step are already done. Otherwise it gets lost trying different paths. This is runnable; 8 years ago I ported it to GNU Common LISP. Takes under a second now. Took 45 minutes in 1980. This is pure constructive mathematics. No existence proofs. Everything is computable. The first theorem: X + 0 = X In Boyer-Moore notation: (PROVE-LEMMA PLUS-0 (REWRITE) (EQUAL (PLUS X 0) (FIX X))) FIX is just if it's a number, then the number, else 0. That makes the function total. The second theorem: X + (Y + 1) = (1 + (X + Y) (PROVE-LEMMA PLUS-ADD1 (REWRITE) (EQUAL (PLUS X (ADD1 Y)) (I
Read the rest
F (NUMBERP Y) (ADD1 (PLUS X Y)) (ADD1 X)))) ADD1 is a primitive of Boyer-Moore theory. ADD is actually recursive ADD1. The user just lists the proof goals. Here's the input file.[3] The proofs are all automatic. This is more automation than many modern provers seem to have. Toward the end, it proves McCarthy's theory of arrays, something I wrote. McCarthy stated those as axioms, but they are in fact provable. The proofs are clunky because there's no set theory yet. I should have built up constructive set theory first, but I didn't realize that back then. [1] https://github.com/John-Nagle/nqthm [2] https://github.com/John-Nagle/pasv/blob/master/src/work/temp... [3] https://github.com/John-Nagle/pasv/blob/master/src/work/temp...
In Natural Number Game: build the basic theory of the natural numbers from scratch
I've written in Common LISP. Here's my port of the Boyer-Moore theorem prover to CLisp.[1] I've used original INTERLISP. I've used a Symbolics LISP machine. I once did a lot of work in Franz LISP.[2] And I'm partially responsible for AutoCAD going with AutoLISP as a scripting language. I've even taken a class from John McCarthy himself, at Stanford. All that was decades ago. I haven't written a new program in LISP in decades. (In later years Python, Go, Rust, C++, and when forced, JavaScript.) * Early thinking was that programs needed to be able to modify themselves. This turned out to be a niche feature. It was a holdover from the early days of programming, where storing into the code was considered a normal activity. Von Neumann was into that. To index into an array, load instructions were modified in place. Once index registers were in
Read the rest
vented, that mostly became unnecessary. Languages can be too dynamic. You pay a price for that. Python suffers from the design feature that anything can store into anything at any time. This blocks most compile-time optimizations. * Lists as a primitive are OK, but modifying lists all the time isn't what you really want. Most languages have settled on a useful set of collections today, and their implementations, especially hashes, are highly optimized. Python pretty much got this right, and other languages now use roughly Python's various collection types. * Saving the entire state of the system, instead of having source files, was a really bad idea for anything that had more than one person working on it. * The overly clever macro system makes code hard to maintain. (I tried to port [2] to Common LISP. Got about half way. It has the original Oppen-Nelson simplifier inside, the first of what's now called a SAT solver. Originally written in MACLISP (MIT's Project MAC, not Apple Macintosh), and ported to Franz LISP by Oppen and Nelson, it has too many clever macros that I wasn't able to completely figure out a decade ago.) This is a generic problem with macro systems, of course, which is why C deliberately had a weak macro system. LISP is a blast from the path. It's fun for retro reasons, but things have moved on. [1] https://github.com/John-Nagle/nqthm [2] https://github.com/John-Nagle/pasv/tree/master/src/CPC4
In Why is Common Lisp not the most popular programming language?
His argument against infinities is interesting. He's making an argument for finite constructive mathematics. It's interesting to see that coming around again. Several decades ago, when I was working on early program proof of correctness, I spent a lot of time with the original Boyer-Moore theorem prover. This is a constructive theory of mathematics, similar to the Piano axioms. It really is possible to have useful mathematics without undecidablity. But you have to give up infinity. Boyer-Moore theory has recursion, but all recursions must have an upper bound. There must be some nonnegative integer that gets smaller with each recursion. Set theory is possible in the Boyer-Moore system. Boyer and Moore usually added the traditional axioms of set theory. But I tried doing without that to get to a McCarthy-like theory of arrays without set th
Read the rest
eory first. Arrays had a concrete representation as an ordered sequence of (index, value) tuples. The sequence has to be ordered so that array equality works. So all the functions for operating on arrays are rather clunky. Here's the sequence of machine proofs for that.[3] Back then, mathematicians hated such approaches. Infinity is a useful trick for handling the edge cases within the main case. Formulas become simpler. You can do things on blackboards with chalk more easily. Constructive math is boring and kind of ugly, because there's a lot of case analysis. But it's sound. You can avoid undecidability and Russel's class of all classes paradox. You don't actually need infinities for much of mathematics. Today there's more acceptance of machine proofs and case analysis. Everybody has computers, after all. I'm not a mathematician; I just used formal math as a tool to improve programs. This is a problem for the theorists. (A few years ago, I converted the original Boyer-Moore code to run under Gnu Common LISP, so it can be run today.[2] Proving the basics of number theory now takes under a second. It took about 45 minutes in 1981.) [1] https://sites.math.rutgers.edu/~zeilberg/Opinion160.html [2] https://github.com/John-Nagle/nqthm [3] https://github.com/John-Nagle/pasv/blob/master/src/work/temp...
In Opinions of Doron Zeilberger
> In the 70's, this wasn't considered a 'real' proof. I ran into that decades ago. We used the original Boyer-Moore theorem prover [1] as part of a program verification system. The system had two provers, the Nelson-Oppen simplifier (the first SAT solver) to automatically handle the easy proofs, and the Boyer-Moore system for the hard ones. To make sure that both had consistent theories, I used the Boyer-Moore prover to prove the "axioms" of the Nelson-Oppen system, especially what are usually called McCarthy's axioms (the ones that use Select and Store) for arrays. The Boyer-Moore system uses a strictly constructive approach to mathematics. It starts from something like Peano arithmetic (there is a number zero, and an operation add 1) and builds up number theory. So I added a concept of arrays, represented as (index, value) tuples in sor
Read the rest
ted order, and was able to prove the usual "axioms" for arrays as theorems. The machine proofs were long and involved much case analysis.[2] I submitted a paper to JACM in the early 1980s and got back reviews saying that it was just too long and inelegant to be at the fundamentals of computer science. That might not be the case today. A few years back, I put the Boyer-Moore prover on Github, after getting it to work with Gnu Common LISP. So you can still run all this 1980s stuff. It's much faster today. It took about 45 minutes to grind through these proofs on a VAX 11/780 in the early 1980s. Now it takes about a second. The proof log [2] is amusing. It's building up number theory from a very low level, starting by proving that X + 0 = X. Each theorem proved can be used as a lemma by later theorems, so you guide the process by giving it problems to solve in the right order. By line 1900, it's proving that multiplication distributes over addition. Array theory, the new stuff, starts around line 2994. The reason this is so complicated and ugly is that there's no use of axiomatic set theory. Arrays are easy if you have sets. But there are no sets here. Sets don't fit well into this strict constructive theory, because EQUAL means identical. You can't create weaker definitions of equality which say that two sets are equal if they contain the same elements regardless of order, because that introduces a risk of unsoundness. Effort must be put into keeping the tuples of the array representation in ascending order by subscript, which implies much case analysis. Mathematicians hate case analysis. Computers are good at it. [1] https://github.com/John-Nagle/nqthm [2] https://github.com/John-Nagle/pasv/blob/master/src/work/temp...
In Will Computers Redefine the Roots of Math?
I went down this road a long time ago, for a different reason. There are classic "axioms" for arrays, first written down by John McCarthy.[1][2] Those are classically considered to be axioms. But I set out to prove them via constructive means. I was using the Boyer-Moore theorem prover, which starts from something close to Peano arithmetic and builds up number theory from there. In Boyer-Moore theory, everything is represented as a LISP S-expression. Equality is defined as as the expressions being identical. So, in Boyer-Moore theory, you have ordered lists, but not sets. A set, remember, can be informally defined as a collection in which each item appears exactly once. The order of the items doesn't matter. In Boyer-Moore theory, short of forcing in a new axiom, which risks soundness, there's no way to say "the order of the items doesn't
Read the rest
matter". At startup, there's no notion of a "set". But you can build one up via definitions and theorems. So I started by defining an array as an list of (subscript, value) tuples, ordered by subscript. Two arrays are equal if they are both valid and identical. So, given that definition, we can write the classic "axioms" for arrays as statements to be proven. First, we define "storea(A,I,V)" as the function that stores into an array in a functional sense - it creates a new array where element I has been replaced with V. And we define the predicate "arrayp", which tests whether an array is properly constructed. The key "axiom" for arrays, in Boyer-Moore notation, reads: (PROVE-LEMMA SELECT-OF-STORE (REWRITE) (IMPLIES (AND (arrayp A) (NUMBERP I) (NUMBERP J)) (EQUAL (selecta (storea A I V) J) (IF (EQUAL I J) V (selecta A J))))) That means, if A is an array, and I and J are integers, then, for all A, I, and J: storea(A,I,V)[J] = (if i=j then V else A[J]) So that's McCarthy's key axiom for arrays. It's not an axiom here. It's a lemma to be proven. It turns out that with the right sequence of lemmas, that statement can be proved as a theorem. Here's that sequence.[3] I wrote that around 1981, and it went through the Boyer-Moore theorem prover. So we could then load those statements into the Oppen-Nelson simplifier (what today is called a SAT solver) as safely proven rewrite rules. This established that the array theory was both sound and consistent between the two provers. A few years back, I revived the original Boyer-Moore theorem prover, converted it to GNU Common LISP, and put it on Github.[4] You can download it, run it, and run the set of rules in [3] through it to get the proofs. (That took about 45 minutes of VAX time in 1981. Now it takes about a second.) The proofs are long, because there's a lot of case analysis. Arrays are ordered lists of tuples in this theory, and there's a lot of detail about maintaining that order. We had to prove (PROVE-LEMMA STORE-IS-PROPER (REWRITE) (EQUAL (arrayp (storea A I V)) T)) which proves that the storing operation always produces a validly ordered array. That's essentially a code proof of correctness for a recursive function The Boyer-Moore prover was able to grind out a proof of that without help. That was a long proof, too. I submitted this to JACM. It was rejected, mostly for uglyness. The concept that you needed all this heavy machine-driven case analysis to prove a nice simple "axiom" upset mathematicians. Today it would be less of an issue. People are now more used to proofs that take a lot of grinding through cases. You could build up set theory this way, via ordered lists, if you wanted. So that's a classic of what happens if you take "equal" seriously. [1] http://www-formal.stanford.edu/jmc/towards.pdf [2] https://theory.stanford.edu/~arbrad/papers/arrays.pdf [3] https://github.com/John-Nagle/pasv/blob/master/src/work/temp... [4] https://github.com/John-Nagle/nqthm
In Grothendieck's Approach to Equality [pdf]
Back when I was working on proof of correctness, when that was a very new thing, I was using the Boyer-Moore theorem prover remotely on a large time-shared mainframe at SRI International. At the time, you needed a mainframe to run LISP. I was working on proofs of basic numeric functions for bounded arithmetic. So I was writing theorems with numbers such as 65536. This caused the mainframe to run out of memory, page out to disk, and thrash, bringing other users to a crawl. It took a while to figure out why relatively simple theorems were doing this. Boyer and Moore explained to me that the internal representation of numbers was exactly that of their constructive mathematics theory. 2 was (ADD1 (ADD1 (ZERO))). 65536 was a very long string of CONS cells. I was told that most of their theorems involved numbers like 1. They went on to improve
Read the rest
the number representation in their prover, after which it could prove useful theorems about bounded arithmetic. (I still keep a copy of their prover around. It's on Github, at [1]. It's several thousand times faster on current hardware than it was on the DECSYSTEM 2060.) [1] https://github.com/John-Nagle/nqthm
In Ask HN: Best “I brought down production” story?
"The intuitively attractive ideal of having a system in which every true statement you care about can be proved is not achievable". Sure it is. But the system cannot contain infinities or real numbers of infinite precision. The halting problem for deterministic computers with finite memory is solveable - the system must either halt or repeat a state, and if it repeats a state, it's in an endless loop. Yes, the number of states may be very large, but not infinite. An interesting question to ask is "do we need infinities". In a sense, infinities are a convenience feature to make formulas simpler. Without infinities, everything has edge cases, bounds problems, tolerances, and all the annoyances known to people who do serious computer number-crunching. Theory in that area has lots of case analysis. It's perhaps unfortunate that Russell lived
Read the rest
in the pre-computer era. Simple theorems were essential in the paper and pencil era. In the 1970s and 1980s, Boyer and Moore took on much the same problem as Principia Mathematica. They published "A Computational Logic"[1], and built the first good inductive theorem prover for constructive mathematics. (I used it back then, and a few years ago I got it running again on Common LISP and put it on Github, so you can still run it.[2]) Proofs that come out of that thing have lots of case analysis. Mathematicians used to hate proofs like that. Many still do. At one point Boyer and Moore formalized what a floating point unit does, and this was used to verify an AMD floating point unit. This followed an embarrassing recall of Intel CPUs that were slightly off on division, and was paid for by AMD. It's a huge, ugly problem to formalize, compared to the ordinary rules for real arithmetic. It is, however, a system in which every true statement you care about can be proved. The proof of the four-color theorem shook up the field. Thousands of cases. A simple problem with a huge proof that required machine assistance was a new thing back then. Now it's more common. Still makes many mathematicians unhappy. [1] https://www.cs.utexas.edu/users/boyer/acl.pdf [2] https://github.com/John-Nagle/nqthm
In What people get wrong about Bertrand Russell
Yes. Been there, done that, a long time ago, and had the same political problems. Many years ago, around 1980, I headed a project to build one of the first program proof of correctness systems. (It's on Github now, about two-thirds working with modern compilers.)[1] We succeeded, but it was just too hard to use. And, back then, really slow. As in 15 minutes to hours. We had two theorem provers. One was what's now called an SMT solver - it could prove theorems with addition, subtraction, multiplication by constants, equalities, inequalities, and the Boolean operators. It could also accept rewrite rules, which it accepted as true. Because allowing users to input rewrite rules made it easy to create false proofs, we required that the rewrite rules be proven using the Boyer-Moore theorem prover, which is a pure constructive system.[2] Then th
Read the rest
e theorems could be transferred to the SMT solver and used there. One of the things we set out to prove were the "axioms" for arrays. There are four, credited to McCarthy in his classic 1962 paper. But we wanted to prove them from a theory of constructive arrays. In Boyer-Moore theory, we defined arrays as sorted lists of (subscript, value) tuples. And then we set out to prove the classic four axioms for arrays with machine proofs. Which we did.[3], starting at line 3372. That file is the Boyer-Moore theorem prover grinding its way up from a very low base of number theory to the theorems we needed for our program proving system. (On a modern computer, in seconds. Took 3 hours on a VAX in 1980. We just didn't have enough engine for this stuff back then.) In that notation, "(selecta A I)" means A[I], and "(storea A I V)" is array A with A[I] replaced with V. After some struggles, we got the the prover to find a way to prove that. But it's ugly. There's lots of case analysis, which mathematicians hate. We didn't get to redefine "equal", so we couldn't have abstract sets. We had to have ordered lists of tuples, which meant all the fussyness of updating an ordered list representation and proving that was sound. But it all worked. So I submitted this to JACM, back when that mattered. It was rejected. The comments were along the lines of "I don't like having something that ugly at the foundations of computer science". The reviewers all agreed it was valid, but they just didn't like doing it that way, in an automatic but inelegant way. Looks like the author of the original article here is still having that problem. Good times back then. But we were way ahead of our time. [1] https://github.com/John-Nagle/pasv [2] https://github.com/John-Nagle/nqthm [3] https://github.com/John-Nagle/pasv/blob/master/src/work/temp...
In The Future of Mathematics? [pdf]
Having been down this road [1], a big problem is that the people doing this tend to be too much into the theory and not enough into wanting programs not to crash. That's some of the Coq world. It doesn't have to be this way. Microsoft's work in this area led to the Static Driver Verifier [2], which was a huge success. Windows crashes due to kernel drivers breaking the rest of the kernel have pretty much disappeared. They were once (Windows XP and earlier) responsible for over half of Windows crashes. Most of the things you want to prove about programs to make sure they don't crash are mathematically trivial. Detecting buffer overflows and off-by-one errors seldom requires elaborate proofs. If they do, your program design probably has a problem. Simple proofs can be handled by a prover which uses a Nelson-Oppen decision procedure. This i
Read the rest
s a complete decision procedure for formulas which contain only addition, subtraction, multiplication by constants, Boolean operators, structures, and arrays. This covers most data access operations. It's fully automatic and complete - the algorithm always succeeds within its problem space. You need one of those for the easy parts. For the hard parts, we used the Boyer-Moore prover. (Sources are available and I brought it back to life on Github.[3] It runs 3000x faster than it did in 1982.) The interface with the language is crucial to usability. Most efforts get this wrong. They put the verification stuff in separate files, or in comments. It should be part of the normal code. We built the verification language into the compiler, so you used the same source and the same syntax for compiling and verification. There were additional statements - ASSERT, ENTER, EXIT, etc. The same compiler front end was used for both compiling and verification. The verifier itself ran off the byte code from the compiler front end. All type and size issues had been decided at that point, so the verifier was looking at unambiguous operations such as "pop two operands from stack, add in 16-bit twos complement arithmetic, push on stack". This captured the semantics of the program the same way the compiler did. The user never sees the easy stuff. Every subscript use generates a bounds check assertion which has to be proved. If the prover is able to prove the assertion true, the user doesn't see it. Only the errors are visible. Another trick is that you don't write "assert(a and b and and d)", you write "assert(a); assert(b);". Those generate separate proof goals, and if only one fails,the user gets error messages about only that one. Also, when you write "assert(a); assert(b);", the second one is proved under the assumption the first one is true. This prevents cascading errors. A key point here is that verification is meaningless unless you prove all the theorems. If you write "assert(false)" other errors go away, and you're left with one simple error, which you cannot eliminate. You don't get to dismiss it. It's possible to write an assertion that's equivalent to "assert(false);", such as "assert(0 > 1)"; This is why I'm so negative on the arguments from some of the Rust crowd that a little undefined behavior is OK. It can blow at any seam. When you needed to prove something hard, you wrote assert(a); assert(b); where a could be proved by the Nelson-Oppen prover from the code before it. B was chosen such that if you assumed b, the Nelson-Oppen prover could prove anything required past that point. Now you had one error message, that the system couldn't prove "a implies b". So then someone had to try to prove that with the Boyer-Moore prover. The Boyer-Moore prover is mostly automatic, but often someone had to provide intermediate theorems to get to a hard one. Those were all machine-checked. If this succeeded, the Boyer-Moore prover generated a theorem that a implies b for use by the verifier. Rerunning the program verifier would then succeed. Such theorems could be reused with different variable bindings, so if the same problem came up again, it didn't require more work with the Boyer-Moore system. If you're generating vast amounts of user-visible proof work, something is wrong. The whole system I'm describing is now on Github.[4] It's decades-old code, but I've ported about 80% of it to modern compilers. The Franz LISP to Common LISP port of the prover isn't working well, and the author of that code died years ago. If anyone wants to work on that, go ahead. [1] http://www.animats.com/papers/verifier/verifiermanual.pdf [2] https://docs.microsoft.com/en-us/windows-hardware/drivers/de... [3] https://github.com/John-Nagle/nqthm [4] https://github.com/John-Nagle/pasv
In Formal Verification: The Gap Between Perfect Code and Reality
It's good to know that Boyer and Moore are still at it. I used their previous system, Nqthm, many years ago. I recently got it running from the 1992 sources and put it on Github.[1] It's nice seeing it run a thousand times faster. I still like their logic system. Constructive mathematics has a basic soundness you don't get from systems with too many axioms. [1] https://github.com/John-Nagle/nqthm
In ACL2
I had a run-in with this issue once. Many years ago, when working on a program verification system, I had to show that two theorem provers (Boyer-Moore and Nelson-Oppen) were working on compatible theories. If that was botched, the system could prove false statements. So I undertook to prove, in Boyer-Moore theory, the "axioms" of arrays used in the Nelson-Oppen prover. McCarthy defined four classic axioms for arrays, and here are the first two. p=r ⇒ read(write(a, p, v), r) = v ¬(p=r) ⇒ read(write(a, p, v), r) = read(a, r) This just says that if you store v at a[p], and then read it back, you get what you put there, and you don't change any other elements. So I set this up in Boyer-Moore notation. (PROVE-LEMMA SELECT-OF-STORE-1 (REWRITE) (IMPLIES (AND (arrayp A) (NUMBERP I)) (EQUAL (selecta (stor
Read the rest
ea A I V) I) V))) (PROVE-LEMMA SELECT-OF-STORE-2 (REWRITE) (IMPLIES (AND (arrayp A) (NUMBERP I) (NUMBERP J) (NOT (EQUAL I J))) (EQUAL (selecta (storea A I V) J) (selecta A J)))) But Boyer-Moore theory is constructive; everything must have a definition that can be evaluated as a function. So I had to define arrays and the "selecta" and "storea" functions so that this would work. I could do this by defining an array as a list of (subscript, value) tuples. (ADD-SHELL UNDEFINED-OBJECT UNDEFINED UNDEFINEDP ()) (ADD-SHELL array-shell empty-array array-recognizer ((array-elt-value (NONE-OF) UNDEFINED) (array-elt-subscript (ONE-OF NUMBERP) ZERO) (array-prev (ONE-OF array-recognizer) empty-array))) This is simply a structure definition for a list. An array-shell has an array-elt-value, an array-elt-subscript, and array-prev, which is the rest of the elements. "array-shell" is the constructor, "empty-array" is essentially "nil" for this type, and "array-recognizer" is a predicate which is true for objects of type array-shell. For array equality to work, the list of tuples had to be ordered by subscript, so that two arrays with the same values would always have the same representation. So selecta and storea are complicated. selecta is a recursive lookup in a list: (DEFN selecta (A I) (IF (EQUAL (array-elt-subscript A) I) (array-elt-value A) (IF (EQUAL (array-prev A) (empty-array)) (UNDEFINED) (selecta (array-prev A) I)))) Store into array is ugly, because it's an update of an ordered list. (DEFN storea (A I V) (IF (NOT (arrayp A)) (empty-array) (IF (NOT (NUMBERP I)) A (IF (EQUAL A (empty-array)) (IF (EQUAL V (UNDEFINED)) (empty-array) (array-shell V I (empty-array))) (IF (EQUAL (array-elt-subscript A) I) (IF (EQUAL V (UNDEFINED)) (array-prev A) (array-shell V I (array-prev A))) (IF (LESSP (array-elt-subscript A) I) (IF (EQUAL V (UNDEFINED)) A (array-shell V I A)) (array-shell (array-elt-value A) (array-elt-subscript A) (storea (array-prev A) I V)))))))) This recursive function is complicated enough that it needs a lemma to help with proof of termination. (PROVE-LEMMA storea-is-sound (REWRITE) (implies (and (arrayp a) (not (equal a (empty-array)))) (lessp (count (array-prev a)) (count a)))) This says that the array-prev list component of a is always shorter than a. The Boyer-Moore prover must prove termination for all defined functions, which it does by examining any recursive call and proving that there is a measure defined on the arguments which is a smaller nonnegative integer. For the easy cases, the prover can do this automatically, but sometimes you have to define a lemma to help it. The lemma, of course, also has to be proved. So, given all that, the Boyer-Moore theorem prover could be turned loose to grind out proofs of the rules for arrays. It took about 45 minutes of grinding in 1982 on a 1 MIPS VAX 11/780. You can rerun it now. Here's the prover, which requires clisp.[1]. Here's the entire set of definitions and theorems we used for program verification.[2]. Read the instructions at [1], and get the system up to where it's done "boot-strap". Then load the file at [2]. Long, detailed, human-readable explanations of the proof steps are displayed. The entire proof run for all those basic theorems takes about a second, and that's probably mostly scrolling. So, having rigorously proved the "axioms" for arrays from first principles, I submitted a paper to JACM. The reviewer comments I got back were mainly about the uglyness of the approach. All that case analysis! One comment was that the reviewer was upset about having a proof that ugly at the foundations of computer science. The paper was rejected. The reason it's ugly is that it doesn't get to use set theory. Informally, a set is a collection whose order does not matter. "Order does not matter" is not a meaningful concept in strict Boyer-Moore theory. If order matters, there are many cases to prove, stemming from all those IF statements in the definitions. A generation later, fewer mathematicians would be bothered by this form of "uglyness". But back then, machine proofs were so rare that this was considered awful. Also, now that computers can do this in a second, complaining that it's too complicated seems whiny. [1] https://github.com/John-Nagle/nqthm [2] https://github.com/John-Nagle/pasv/blob/master/src/work/temp...
In What makes maths beautiful?
There's another approach, from Boyer and Moore. Boyer and Moore built up mathematics from constructs at the Peano axiom level (zero, add1, etc.) plus recursive functions that must terminate. It's constructive mathematics; there are no quantifiers, no ∀ or ∃. [1] They built an automatic theorem prover in the 1970s and 1980s that works on this theory. (I recently made it work on Gnu Common LISP and put it on Github, so people can run it again.)[2] In Boyer-Moore theory, all functions are total - you can apply any function to any object. Types are predicates. Here's a definition of ORDERED for a list of number: (DEFN ORDERED (L) (IF (LISTP L) (IF (LISTP (CDR L)) (IF (LESSP (CADR L) (CAR L)) F (ORDERED (CDR L))) T) T)) If
Read the rest
L is not a list, it is considered to be ordered. This makes it a total function, runnable on any input, even though the result for the "wrong" type is not useful. This provides the benefits of types without requiring a theory of types. It's a very clean way to look at the foundations of mathematics. It's simpler than Russell and Whitehead. When you prove things using definitions like this, there's a lot of case analysis. This gets worse combinatorially; a theorem with four variables with type constraints will generate at least 2⁴ cases, only one of which is interesting. Most of the cases, such as when L is not a list, are trivial, but have to be analyzed. Computers are great at this, and the Boyer-Moore prover deals with those cases without any trouble. But it went against a long tradition in mathematics of avoiding case analysis. That made this approach unpopular with the generation of pre-computer mathematicians. Today, it would be more acceptable. (It's fun to run the Boyer-Moore prover today. In the 1980s, it took 45 minutes to grind through the basic theory of numbers. Now it takes a few seconds.) [1] https://www.amazon.com/Computational-Logic-Robert-S-Boyer/dp... [2] https://github.com/John-Nagle/nqthm
In Where Do Type Systems Come From?

Showing the 15 most recent of 20 sources. Enable JavaScript to search and browse every indexed mention.

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.