How to fix loop in rewriting proof - acl2

I'm trying to model natural numbers in unary notation (O, (S O), (S (S O)), ...) in ACL2 and prove commutativity of addition. Here is my attempt:
; a NATURAL is 'O or a list ('S n') where n' is a NATURAL
(defun naturalp (n)
(cond ((equal n 'O) t)
(t (and (true-listp n)
(equal (length n) 2)
(equal (first n) 'S)
(naturalp (second n))))))
(defun pred (n)
(cond ((equal n 'O) 'O)
((naturalp n) (second n))))
(defun succ (n)
(list 'S n))
(defun plus (n m)
(cond ((equal n 'O) m)
((naturalp n) (succ (plus (pred n) m)))))
; FIXME: cannot prove this because rewriting loops...
(defthm plus_comm
(implies (and (naturalp n) (naturalp m))
(iff (equal (plus n m) (plus m n)) t)))
This is probably not the most LISPy way of doing this, I'm used to languages with pattern matching.
My problem is as suggested by the comment: The prover loops, trying to prove more and more deeply nested versions of the same thing. How do I stop this? The manual does briefly mention looping rewrite rules, but it doesn't say anything about what to do about them.
My expectation was that this proof would fail, giving me hints as to what auxiliary lemmas are needed to complete it. Can I use the output from the looping proof to figure out a lemma that might stop the looping?

ACL2 can end up getting into different kinds of loops. One common kind is a rewriter loop, which usually is very evident. For example, the following:
(defun f (x) x)
(defun g (x) x)
(defthm f-is-g (implies (consp x) (equal (f x) (g x))))
(defthm g-is-f (implies (consp x) (equal (g x) (f x))))
(in-theory (disable f g))
(defthm loop (equal (f (cons a b)) (cons a b)))
Provokes a rewrite loop, and gives an informative debugging message:
HARD ACL2 ERROR in REWRITE: The call depth limit of 1000 has been
exceeded in the ACL2 rewriter. To see why the limit was exceeded,
first execute
:brr t
and then try the proof again, and then execute the form (cw-gstack)
or, for less verbose output, instead try (cw-gstack :frames 30). You
will then probably notice a loop caused by some set of enabled rules,
some of which you can then disable; see :DOC disable. Also see :DOC
rewrite-stack-limit.
Unfortunately your example is getting into a different kind of loop. In particular, it looks like ACL2 is getting into a loop where
it inducts, but then gets to a subgoal it can't prove by rewriting, so
it inducts, but then gets to a subgoal it can't prove by rewriting, so
...
It's not really as easy to see that this is what's going on. The thing I did was just run (set-gag-mode nil) before submitting the theorem, then inspect the output that was printed after interrupting the prover.
One way to avoid this is to give a hint, in particular you can tell ACL2 not to induct like this:
(defthm plus_comm
(implies (and (naturalp n) (naturalp m))
(iff (equal (plus n m) (plus m n)) t))
:hints(("Goal" :do-not-induct t)))
But if you do that then it gets stuck right away, because probably you really do want to induct to prove this theorem. So what you really want to tell it is: "induct once, but don't induct more than that." The syntax is kind of goofy:
(defthm plus_comm
(implies (and (naturalp n) (naturalp m))
(iff (equal (plus n m) (plus m n)) t))
:hints(("Goal"
:induct t ;; Induct once.
:do-not-induct t ;; But don't induct more than once.
)))
This should leave you with a sensible checkpoint that you can then try to debug by adding rewrite rules or giving further hints.
Good luck!

Related

Scheme: Compiling with continuations

im currently writing a compiler in OCaml for a subset of scheme and am having trouble understanding how to compile with continuations. I found some great resources, namely:
The cps slides of the cmsu compiler course:
https://www.cs.umd.edu/class/fall2017/cmsc430/
This explanation of another cs course:
https://www.cs.utah.edu/~mflatt/past-courses/cs6520/public_html/s02/cps.pdf
Matt Mights posts on a-normal form and cps:
http://matt.might.net/articles/a-normalization/ and
http://matt.might.net/articles/cps-conversion/
Using the anormal transformation introduced in the anormal-paper, I now have code where function calls are either bound to a variable or returned.
Example:
(define (fib n)
(if (<= n 1)
n
(+ (fib (- n 1))
(fib (- n 2)))))
becomes:
(define (fib n)
(let ([c (<= n 1)])
(if c
n
(let ([n-1 (- n 1)])
(let ([v0 (fib n-1)])
(let ([n-2 (- n 2)])
(let ([v1 (fib n-2)])
(+ v0 v1)))))))
In order to cps-transform, I now have to:
add cont-parameters to all non-primitive functions
call the cont-parameter on tail-positions
transform all non-primitive function calls, so that they escape the let-binding and become an extra lambda with the previous let-bound variable as sole argument and the previous let-body
as the body
The result would look like:
(define (fib n k)
(let ([c (<= n 1)])
(if c
(k n)
(let ([n-1 (- n 1)])
(fib n-1
(lambda (v0)
(let ([n-2 (- n 2)])
(fib n-2
(lambda (v1)
(k (+ v0 v1))))))))))
Is this correct?
The csmu-course also talks about how Programs in CPS require no stack and never return. Is that because we don't need to to save the adresses to return to and closures as well as other datatypes are stored on the heap and references are kept alive by using the closures?
The csmu also talks about desugaring of call/cc:
(call/cc) => ((lambda (k f) (f k k)))
when using such desugaring, how does:
(+ 2 (call/cc (lambda (k) (k 2))))
in MIT-Scheme return 4, since the current continuation would probably be something like display?
is this correct?
(define (fib n k)
(let ([c (<= n 1)])
(if c
(k n)
(let ([n-1 (- n 1)])
(fib n-1
(lambda (v0)
(let ([n-2 (- n 2)])
(fib n-2
(lambda (v1)
(k (+ v0 v1))))))))))
you get an A+ 💯
The csmu-course also talks about how Programs in CPS require no stack and never return. Is that because we don't need to to save the addresses to return to and closures as well as other datatypes are stored on the heap and references are kept alive by using the closures?
Exactly! See Chicken Complilation Process for an in-depth read about such a technique.
The csmu also talks about desugaring of call/cc:
(call/cc) => ((lambda (k f) (f k k)))
That doesn't look quite right. Here's a desugar of call/cc from Matt Might -
call/cc => (lambda (f cc) (f (lambda (x k) (cc x)) cc))
The essence of the idea of compiling with continuations is that you want to put an order on the evaluation of arguments passed to each function and after you evaluate that argument you send its value to the continuation passed.
It is required for the language in which you rewrite the code in CPS form to be tail recursive, otherwise it will stack empty frames, followed only by a return. If the implementation language does not impose tail-recursion you need to apply more sophisticated methods to get non-growing stack for cps code.
Take care, if you do it, you also need to change the signature of the primitives. The primitives will also be passed a continuation but they return immediately the answer in the passed continuation, they do not create other continuations.
The best reference about understanding how to compile with continuations remains the book of Andrew W. Appel and you need nothing more.

Space complexity of streams in Scheme

I am reading Structure and Interpretation of Computer Programs (SICP) and would like to make sure that my thinking is correct.
Consider the following simple stream using the recursive definition:
(define (integers-starting-from n)
(cons-stream n (integers-starting-from (+ n 1))))
(define ints (integers-starting-from 1))
(car (cdr-stream (cdr-stream (cdr-stream (cdr-stream ints)))))
If we adopt the implementation in SICP, whenever we cons-stream, we are effectively consing a variable and a lambda function (for delayed evaluation). So as we cdr-stream along this stream, nested lambda functions are created and a chain of frames is stored for the evaluation of lambda functions. Those frames are necessary since lambda functions evaluate expressions and find them in the enclosing frame. Therefore, I suppose that in order to evaluate the n-th element of the stream, you need to store n extra frames that take up linear space.
This is different from the behavior of iterators in other languages. If you need to go far down the stream, much space will be taken. Of course, it is possible to only keep the direct enclosing frame and throw away all the other ancestral frames. Is this what the actual scheme implementation does?
Short answer, yes, under the right circumstances the directly enclosing environment is thrown away.
I don't think this would happen in the case of (car (cdr-stream (cdr-stream (cdr-stream (... but if you instead look at stream-refin sect. 3.5.1:
(define (stream-ref s n)
(if (= n 0)
(stream-car s)
(stream-ref (stream-cdr s) (- n 1))))
and if you temporarily forget what you know about environment frames but think back to Chapter 1 and the disussion of recursive vs iterative processes, then this is a iterative process because the last line of the body is a call back to the same function.
So perhaps your question could be restated as: "Given what I know now about the environmental model of evaluation, how do iterative processes use constant space?"
As you say it's because the ancestral frames are thrown away. Exactly how this happens is covered later in the book in chapter 5, e.g., sect. 4.2 "Sequence Evaluation and Tail Recursion", or if you like the videos of the lectures, in lecture 9b.
A significant part of Chapter 4 and Chapter 5 covers the details necessary to answer this question explicitly. Or as the authors put it, to dispel the magic.
I think it's worth pointing out that the analysis of space usage in cases like this is not always quite simple.
For instance here is a completely naïve implementation of force & delay in Racket:
(define-syntax-rule (delay form)
(λ () form))
(define (force p)
(p))
And we can build enough of something a bit compatible with SICP streams to be dangerous on this:
(define-syntax-rule (cons-stream kar kdr)
;; Both car & cdr can be delayed: why not? I think the normal thing is
;; just to delay the cdr
(cons (delay kar) (delay kdr)))
(define (stream-car s)
(force (car s)))
(define (stream-cdr s)
(force (cdr s)))
(define (stream-nth s n)
(if (zero? n)
(stream-car s)
(stream-nth (stream-cdr s) (- n 1))))
(Note there is lots missing here because I am lazy.)
And on that we can build streams of integers:
(define (integers-starting-from n)
(cons-stream n (integers-starting-from (+ n 1))))
And now we can try this:
(define naturals (integers-starting-from 0))
(stream-nth naturals 10000000)
And this last thing returns 10000000, after a little while. And we can call it several times and we get the same answer each time.
But our implementation of promises sucks: forcing a promise makes it do work each time we force it, and we'd like to do it once. Instead we could memoize our promises so that doesn't happen, like this (this is probably not thread-safe: it could be made so):
(define-syntax-rule (delay form)
(let ([thunk/value (λ () form)]
[forced? #f])
(λ ()
(if forced?
thunk/value
(let ([value (thunk/value)])
(set! thunk/value value)
(set! forced? #t)
value)))))
All the rest of the code is the same.
Now, when you call (stream-nth naturals 10000000) you are probably going to have a fairly bad time: in particular you'll likely run out of memory.
The reason you're going to have a bad time is two things:
there's a reference to the whole stream in the form of naturals;
the fancy promises are memoizing their values, which are the whole tail of the stream.
What this means is that, as you walk down the stream you use up increasing amounts of memory until you run out: the space complexity of the program goes like the size of the argument to stream-nth in the last line.
The problem here is that delay is trying to be clever in a way which is unhelpful in this case. In particular if you think of streams as objects you traverse generally once, then memoizing them is just useless: you've carefully remembered a value which you will never use again.
The versions of delay & force provided by Racket memoize, and will also use enormous amounts of memory in this case.
You can avoid this either by not memoizing, or by being sure never to hold onto the start of the stream so the GC can pick it up. In particular this program
(define (silly-nth-natural n)
(define naturals (integers-starting-from 0))
(stream-nth naturals n))
will not use space proportional to n, because once the first tail call to stream-nth is made there is nothing holding onto the start of the stream any more.
Another approach is to make the memoized value be only weakly held, so that if the system gets desperate it can drop it. Here's a hacky and mostly untested implementation of that (this is very Racket-specific):
(define-syntax-rule (delay form)
;; a version of delay which memoizes weakly
(let ([thunk (λ () form)]
[value-box #f])
(λ ()
(if value-box
;; the promise has been forced
(let ([value-maybe (weak-box-value value-box value-box)])
;; two things that can't be in the box are the thunk
;; or the box itself, since we made those ourselves
(if (eq? value-maybe value-box)
;; the value has been GCd
(let ([value (thunk)])
(set! value-box (make-weak-box value))
value)
;; the value is good
value-maybe))
;; the promise has not yet been forced
(let ((value (thunk)))
(set! value-box (make-weak-box value))
value)))))
I suspect that huge numbers of weak boxes may make the GC do a lot of work.
"nested lambda functions are created"
nope. There is no nested scope. In
(define integers-starting-from
(lambda (n)
(cons-stream n (integers-starting-from (+ n 1)))))
the argument to the nested call to integers-starting-from in the (integers-starting-from (+ n 1)) form, the expression (+ n 1), refers to the binding of n in the original call to (integers-starting-from n), but (+ n 1) is evaluated before the call is made.
Scheme is an eager programming language, not a lazy one.
Thus the lambda inside the result of cons-stream holds a reference to the call frame, yes, but there is no nesting of environments. The value is already obtained before the new lambda is created and returned as part of the next cons cell representing the stream's next state.
(define ints (integers-starting-from 1))
=
(define ints (let ((n 1))
(cons-stream n (integers-starting-from (+ n 1)))))
=
(define ints (let ((n 1))
(cons n (lambda () (integers-starting-from (+ n 1))))))
and the call proceeds
(car (cdr-stream (cdr-stream ints)))
=
(let* ((ints (let ((n 1))
(cons n
(lambda () (integers-starting-from (+ n 1))))))
(cdr-ints ((cdr ints)))
(cdr-cdr-ints ((cdr cdr-ints)))
(res (car cdr-cdr-ints)))
res)
=
(let* ((ints (let ((n 1))
(cons n
(lambda () (integers-starting-from (+ n 1))))))
(cdr-ints ((cdr ints))
=
((let ((n 1))
(lambda () (integers-starting-from (+ n 1)))))
=
(integers-starting-from 2) ;; args before calls!
=
(let ((n 2))
(cons n
(lambda () (integers-starting-from (+ n 1)))))
)
(cdr-cdr-ints ((cdr cdr-ints)))
(res (car cdr-cdr-ints)))
res)
=
(let* ((ints (let ((n 1))
(cons n
(lambda () (integers-starting-from (+ n 1))))))
(cdr-ints (let ((n 2))
(cons n
(lambda () (integers-starting-from (+ n 1))))))
(cdr-cdr-ints (let ((n 3))
(cons n
(lambda () (integers-starting-from (+ n 1))))))
(res (car cdr-cdr-ints)))
res)
=
3
So there is no nested lambdas here. Not even a chain of lambdas, because the implementation is non-memoizing. The values for cdr-ints and cdr-cdr-ints are ephemeral, liable to be garbage-collected while the 3rd element is being calculated. Nothing holds any reference to them.
Thus getting the nth element is done in constant space modulo garbage, since all the interim O(n) space entities are eligible to be garbage collected.
In (one possible) memoizing implementation, each lambda would be actually replaced by its result in the cons cell, and there'd be a chain of three -- still non-nested -- lambdas, congruent to an open-ended list
(1 . (2 . (3 . <procedure-to-go-next>)))
In programs which do not hold on to the top entry of such chains, all the interim conses would be eligible for garbage collection as well.
One such example, even with the non-memoizing SICP streams, is the sieve of Eratosthenes. Its performance characteristics are consistent with no memory retention of the prefix portions of its internal streams.

Scheme tail-recursion/iteration

I've built a recursive function in scheme, which will repeat a given function f, n times on some input.
(define (recursive-repeated f n)
(cond ((zero? n) identity)
((= n 1) f)
(else (compose f (recursive-repeated f (- n 1))))))
I need to build an iterative version of this function with tail recursion, which I think I've done right if I understand tail recursion correctly.
(define (iter-repeated f n)
(define (iter count total)
(if (= count 0)
total
(iter (- count 1) (compose f total))))
(iter n identity))
My question is, is this actually iterative? I believe I have it built correctly using tail recursion, but it's still technically deferring a bunch of operations until count = 0, where it executes however many compositions it's stacked up.
You pose a good question. You went from a recursive process (recursive-repeated) which builds a recursive process ((f (f (f ...)))) to an iterative process (iter-repeated) that builds the same recursive process.
You're right in thinking that you've basically done the same thing because the end result is the same. You just constructed the same chain in two different ways. This is the "consequence" of using compose in your implementation.
Consider this approach
(define (repeat n f)
(λ (x)
(define (iter n x)
(if (zero? n)
x
(iter (- n 1) (f x))))
(iter n x)))
Here, instead of building up an entire chain of function calls ahead of time, we'll return a single lambda that waits for the input argument. When the input argument is specified, we will loop inside the lambda in an iterative way for n times.
Let's see it work
(define (add1 x) (+ x 1))
;; apply add1 5 times to 3
(print ((repeat 5 add1) 3)) ;; → 8

Tail recursive functions in Scheme

I'm studying for a Christmas test and doing some sample exam questions, I've come across this one that has me a bit stumped
I can do regular recursion fine, but I can't wrap my head around how to write the same thing using tail recursion.
Regular version:
(define (factorial X)
(cond
((eqv? X 1) 1)
((number? X)(* X (factorial (- X 1))))))
For a function to be tail recursive, there must be nothing to do after the function returns except return its value. That is, the last thing that happens in the recursive step is the call to the function itself. This is generally achieved by using an accumulator parameter for keeping track of the answer:
(define (factorial x acc)
(if (zero? x)
acc
(factorial (sub1 x) (* x acc))))
The above procedure will be initially called with 1 as accumulator, like this:
(factorial 10 1)
=> 3628800
Notice that the accumulated value gets returned when the base case is reached, and that the acc parameter gets updated at each point in the recursive call. I had to add one extra parameter to the procedure, but this can be avoided by defining an inner procedure or a named let, for example:
(define (factorial x)
(let loop ((x x)
(acc 1))
(if (zero? x)
acc
(loop (sub1 x) (* x acc)))))

In Scheme, how do you use lambda to create a recursive function?

I'm in a Scheme class and I was curious about writing a recursive function without using define. The main problem, of course, is that you cannot call a function within itself if it doesn't have a name.
I did find this example: It's a factorial generator using only lambda.
((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))
But I can't even make sense of the first call, (lambda (x) (x x)): What exactly does that do? And where do you input the value you want to get the factorial of?
This is not for the class, this is just out of curiosity.
(lambda (x) (x x)) is a function that calls an argument, x, on itself.
The whole block of code you posted results in a function of one argument. You could call it like this:
(((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))
5)
That calls it with 5, and returns 120.
The easiest way to think about this at a high level is that the first function, (lambda (x) (x x)), is giving x a reference to itself so now x can refer to itself, and hence recurse.
The expression (lambda (x) (x x)) creates a function that, when evaluated with one argument (which must be a function), applies that function with itself as an argument.
Your given expression evaluates to a function that takes one numeric argument and returns the factorial of that argument. To try it:
(let ((factorial ((lambda (x) (x x))
(lambda (fact-gen)
(lambda (n)
(if (zero? n)
1
(* n ((fact-gen fact-gen) (sub1 n)))))))))
(display (factorial 5)))
There are several layers in your example, it's worthwhile to work through step by step and carefully examine what each does.
Basically what you have is a form similar to the Y combinator. If you refactored out the factorial specific code so that any recursive function could be implemented, then the remaining code would be the Y combinator.
I have gone through these steps myself for better understanding.
https://gist.github.com/z5h/238891
If you don't like what I've written, just do some googleing for Y Combinator (the function).
(lambda (x) (x x)) takes a function object, then invokes that object using one argument, the function object itself.
This is then called with another function, which takes that function object under the parameter name fact-gen. It returns a lambda that takes the actual argument, n. This is how the ((fact-gen fact-gen) (sub1 n)) works.
You should read the sample chapter (Chapter 9) from The Little Schemer if you can follow it. It discusses how to build functions of this type, and ultimately extracting this pattern out into the Y combinator (which can be used to provide recursion in general).
You define it like this:
(let ((fact #f))
(set! fact
(lambda (n) (if (< n 2) 1
(* n (fact (- n 1))))))
(fact 5))
which is how letrec really works. See LiSP by Christian Queinnec.
In the example you're asking about, the self-application combinator is called "U combinator",
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
((U h) 5))
The subtlety here is that, because of let's scoping rules, the lambda expressions can not refer to the names being defined.
When ((U h) 5) is called, it is reduced to ((h h) 5) application, inside the environment frame created by the let form.
Now the application of h to h creates new environment frame in which g points to h in the environment above it:
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
( (let ((g h))
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))
5))
The (lambda (n) ...) expression here is returned from inside that environment frame in which g points to h above it - as a closure object. I.e. a function of one argument, n, which also remembers the bindings for g, h, and U.
So when this closure is called, n gets assigned 5, and the if form is entered:
(let ((U (lambda (x) (x x)))
(h (lambda (g)
(lambda (n)
(if (zero? n)
1
(* n ((g g) (sub1 n))))))))
(let ((g h))
(let ((n 5))
(if (zero? n)
1
(* n ((g g) (sub1 n)))))))
The (g g) application gets reduced into (h h) application because g points to h defined in the environment frame above the environment in which the closure object was created. Which is to say, up there, in the top let form. But we've already seen the reduction of (h h) call, which created the closure i.e. the function of one argument n, serving as our factorial function, which on the next iteration will be called with 4, then 3 etc.
Whether it will be a new closure object or same closure object will be reused, depends on a compiler. This can have an impact on performance, but not on semantics of the recursion.
I like this question. 'The scheme programming language' is a good book. My idea is from Chapter 2 of that book.
First, we know this:
(letrec ((fact (lambda (n) (if (= n 1) 1 (* (fact (- n 1)) n))))) (fact 5))
With letrec we can make functions recursively. And we see when we call (fact 5), fact is already bound to a function. If we have another function, we can call it this way (another fact 5), and now another is called binary function (my English is not good, sorry). We can define another as this:
(let ((another (lambda (f x) .... (f x) ...))) (another fact 5))
Why not we define fact this way?
(let ((fact (lambda (f n) (if (= n 1) 1 (* n (f f (- n 1))))))) (fact fact 5))
If fact is a binary function, then it can be called with a function f and integer n, in which case function f happens to be fact itself.
If you got all the above, you could write Y combinator now, making a substitution of let with lambda.
With a single lambda it's not possible. But using two or more lambda's it is possible. As, all other solutions are using three lambdas or let/letrec, I'm going to explain the method using two lambdas:
((lambda (f x)
(f f x))
(lambda (self n)
(if (= n 0)
1
(* n (self self (- n 1)))))
5)
And the output is 120.
Here,
(lambda (f x) (f f x)) produces a lambda that takes two arguments, the first one is a lambda(lets call it f) and the second is the parameter(let's call it x). Notice, in its body it calls the provided lambda f with f and x.
Now, lambda f(from point 1) i.e. self is what we want to recurse. See, when calling self recursively, we also pass self as the first argument and (- n 1) as the second argument.
I was curious about writing a recursive function without using define.
The main problem, of course, is that you cannot call a function within
itself if it doesn't have a name.
A little off-topic here, but seeing the above statements I just wanted to let you know that "without using define" does not mean "doesn't have a name". It is possible to give something a name and use it recursively in Scheme without define.
(letrec
((fact
(lambda (n)
(if (zero? n)
1
(* n (fact (sub1 n)))))))
(fact 5))
It would be more clear if your question specifically says "anonymous recursion".
I found this question because I needed a recursive helper function inside a macro, where one can't use define.
One wants to understand (lambda (x) (x x)) and the Y-combinator, but named let gets the job done without scaring off tourists:
((lambda (n)
(let sub ((i n) (z 1))
(if (zero? i)
z
(sub (- i 1) (* z i)) )))
5 )
One can also put off understanding (lambda (x) (x x)) and the Y-combinator, if code like this suffices. Scheme, like Haskell and the Milky Way, harbors a massive black hole at its center. Many a formerly productive programmer gets entranced by the mathematical beauty of these black holes, and is never seen again.

Resources