Hacker News new | ask | show | jobs
by almostgotcaught 359 days ago
This is proof by exhaustion: the "proof" just computes the entire memo table for any n and compares the values in the table with the corresponding return from recursive definition. You could write this same proof in absolutely any language that supports recursion (or not, if you transform to the bottom-up formulation).
6 comments

In Lean you don't actually have to run this for every n to verify that the algorithm is correct for every n. Correctness is proved at type-checking time, without actually running the algorithm. That's something that you can't do in a normal programming language.
it's explicitly stated in the article:

> For an arbitrary n, compute the table full of values and their proofs, and just pull out the nth proof

if you thought harder about it you'd realize what you're suggesting is impossible

It's not impossible, that's the whole point of a theorem prover. You write a computation, but you don't actually have to run the computation. Simply typechecking the computation is enough to prove that its result is correct.

For example, in a theorem prover, you can write an inductive proof that x^2 + x is even for all x. And you can write this via a computation that demonstrates that it's true for zero, and if it's true for x, then it's true for x + 1. However, you don't need to run this computation in order to prove that it's true for large x. That would be computationally intractable, but that's okay. You just have to typecheck to get a proof.

> you can write an inductive proof that x^2 * (x^2 - 1) is divisible by 4 for all x

my friend you should either read the article more closely or think harder. he's not proving that the recurrence relation is correct (that would be meaningless - a recurrence relation is just given), he's proving that DP/memoization computes the same values as the recurrence relation.

the obvious indicator is that no property of any numbers is checked here - just that one function agrees with another:

  theorem maxDollars_spec_correct : ∀ n, maxDollars n = maxDollars_spec n
this is the part that's undecidable (i should've said that instead of "impossible")

https://en.wikipedia.org/wiki/Richardson%27s_theorem

> my friend you should either read the article more closely or think harder

Hmmm.

> no property of any numbers is checked here - just that one function agrees with another:

    theorem maxDollars_spec_correct : ∀ n, maxDollars n = maxDollars_spec n
> this is the part that's undecidable (i should've said that instead of "impossible")

> https://en.wikipedia.org/wiki/Richardson%27s_theorem

Given that both `maxDollars n` and `maxDollars_spec n` are defined to be natural numbers, I'm not sure why Richardson's theorem is supposed to be relevant.

But even if it was, the structure of the proof is to produce the fact `maxDollars_spec n = maxDollars n` algebraically from a definition, and then apply the fact that equality is symmetric to conclude that `maxDollars n = maxDollars_spec n`. And once again I'm not sure how you could possibly fail to conclude that two quantities are equal after being given the fact that they're equal.

> Given that both `maxDollars n` and `maxDollars_spec n` are defined to be natural numbers, I'm not sure why Richardson's theorem is supposed to be relevant

Did you know that the naturals are a subset of the reals? If Richardson's doesn't convince you there's also

https://en.m.wikipedia.org/wiki/Rice%27s_theorem

> Examples

> Is P equivalent to a given program Q?

Irrespective of where you're convinced it's 100% true that equality of two functions is undecidable in general.

The specification proves a property about an algorithm/function, namely the equivalence between a more complicated memoizing implementation and a simpler direct recursive implementation.

It is also true that no numerical reasoning is happening: the memoized version of any recursive relation will return the same result as the original function, assuming the function is not stateful and will return the same outputs given the same inputs.

However, it is not true to say that it does this by exhaustion, since there are infinitely many possible outputs and therefore it cannot be exhaustively checked by direct computation. The “n” for which we are “taking the proof” is symbolic, and hence symbolic justification and abstract invariants are used to provide the proof. It is the symbolic/abstract steps that are verified by the type checker, which involves only finite reasoning.

Of course, the symbolic steps somewhat mirror the concrete computations that would happen if you build the table, especially for a simpler proof like this. But it also shouldn’t be surprising that a program correctness proof would look at the steps that a program takes and reason about them in an abstract way to see that they are correct in all cases.

> the "proof" just computes the entire memo table for any n

No, this is what would happen _if you ran the proof_, but proofs are not meant to be ran in the first place! The usual goal is proving their correctness, and for that it's enough for them to _typecheck_.

it's explicitly stated in the article:

> For an arbitrary n, compute the table full of values and their proofs, and just pull out the nth proof

if you thought harder about it you'd realize what you're suggesting is impossible

This is the fault of sloppy language. In Lean, _proofs_ (equivalent to functions) and _proof objects/certificates_ (values) need to be distinguished. You can't compute proofs, only proof objects. In the above quote, replace "proof" with "certificate" and you'll see that it's a perfectly valid (if trivial - it essentially just applies a lemma) proof.
a distinction without a difference wrt what i'm pointing out: this proof uses exactly zero mathematics just effectively checks all the values of maxDollars_spec.
> it's explicitly stated in the article: > > > For an arbitrary n, compute the table full of values and their proofs, and just pull out the nth proof

That's what the intermediate function is doing, but _it doesn't need to be executed_ for the final proof to be valid.

> if you thought harder about it you'd realize what you're suggesting is impossible

No, it is perfectly possible. This is not a program running all cases and `assert`ing that they have the expected result, this is a proof encoded as a program that when executed will surely produce a proof that for the given n the theorem holds. But it surely produces a proof when executes you don't need to execute it to know that a proof exists and thus the theorem holds!

This is exactly what mathematicians do when they prove theorems. Do you think they go on and check for every number n that some theorem holds? That's impossible to do for every possible n, and this is exactly what you're claiming you can do in every other language.

And just to be extra clear, a signature like this:

> theorem maxDollars_spec_correct : ∀ n, maxDollars n = maxDollars_spec n

Does not mean that for every n the function returns a boolean indicating whether `maxDollars n` is equal to `maxDollars_spec n` or not. Instead, it _surely_ returns a proof that they are equal. And with _surely_ I mean it cannot return errors or exceptions or whatever. It is guaranteed that the function will terminate with a proof for that proposition, which I want to reiterate again, it's the same as knowing that the proposition is true.

That can't possibly be the case. The thing concluded was that for every n the statement holds. To do that exhaustively for _every_ n requires infinite time. Either their conclusion is incorrect, or your description of the proof is incorrect.
Not if that language doesn't actually check the totality of your proof and ensures that the base case holds.
i don't know what you're saying - here is the proof that is described in the article:

1. build a table tab[n]

2. check that for every i, tab[i] == maxDollars_spec[i]

if you take the latter approach i proposed (bottom up) there is nothing to check the totality of.

> You could write this same proof in absolutely any language that supports recursion

Well, you at least need dependent types just to state the theorem, which eliminates nearly all other languages.

A language without dependent types wouldn't even let you write down the statement of the theorem, so no.