Help understanding Continuations in Scheme - scheme

I have been working alongside The Little Schemer to learn Scheme and using PLT-Scheme for my environment.
The Little Schemer has helped me tremendously with recursion (it is straightforward for me now) but I'm stuck on a portion of the book that introduces "collectors" and calls the function as a whole a continuation.
Here is the example code they have used. I understand the recursive elements but I am stuck, in particular on the lambda functions - my mind can't follow the path and how the arguments for that lambda function are set (since their only call is to call them again in recursion, there is no concrete use within the function body).
If someone could more-or-less give me a break down of the path of computation through the recursion of the function into the lambda collectors, that may help me.
;; Build a nested list of even numbers by removing the odd ones from its
;; argument and simultaneously multiply the even numbers and sum the odd
;; numbers that occur in its argument.
(define (even-only-collector l col)
(cond
((null? l)
(col (quote ()) 1 0))
((atom? (car l))
(cond
((even? (car l))
(even-only-collector (cdr l)
(lambda (newl p s)
(col (cons (car l) newl)
(* (car l) p) s))))
(else
(even-only-collector (cdr l)
(lambda (newl p s)
(col newl
p (+ (car l) s)))))))
(else
(even-only-collector (car l)
(lambda (al ap as)
(even-only-collector (cdr l)
(lambda (dl dp ds)
(col (cons al dl)
(* ap dp)
(+ as ds)))))))))
;; The collector function
(define (collector newl product sum)
(cons sum
(cons product newl)))
Thank you in advance!!

Try something simpler to see how this works. For example, here's a version of a list-sum function that receives a continuation argument (which is often called k):
(define (list-sum l k)
(if (null? l)
???
(list-sum (cdr l) ???)))
The basic pattern is there, and the missing parts are where the interesting things happen. The continuation argument is a function that expects to receive the result -- so if the list is null, it's clear that we should send it 0, since that is the sum:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) ???)))
Now, when the list is not null, we call the function recursively with the list's tail (in other words, this is an iteration), but the question is what should the continuation be. Doing this:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) k)))
is clearly wrong -- it means that k will eventually receive the the sum of (cdr l) instead of all of l. Instead, use a new function there, which will sum up the first element of l too along with the value that it receives:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) (lambda (sum) (+ (car l) sum)))))
This is getting closer, but still wrong. But it's a good point to think about how things are working -- we're calling list-sum with a continuation that will itself receive the overall sum, and add the first item we see now to it. The missing part is evident in the fact that we're ignoring k. What we need is to compose k with this function -- so we do the same sum operation, then send the result to k:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) (compose k (lambda (s) (+ s (car l)))))))
which is finally working. (BTW, remember that each of these lambda functions has its own "copy" of l.) You can try this with:
(list-sum '(1 2 3 4) (lambda (x) x))
And finally note that this is the same as:
(define (list-sum l k)
(if (null? l)
(k 0)
(list-sum (cdr l) (lambda (s) (k (+ s (car l)))))))
if you make the composition explicit.
(You can also use this code in the intermediate+lambda student language, and click the stepper button to see how the evaluation proceeds -- this will take a while to go over, but you'll see how the continuation functions get nested, each with it's own view of the list.)

Here's one way to help you "get a more concrete idea". Imagine if the collector were defined thus:
(define (collector l p s)
(display l)
(newline)
(display p)
(newline)
(display s)
(newline))
You can see in the base case, if you pass in an empty list, it will call your function with arguments '(), 1, and 0. Now, work with a one-element list, and see what it'll call your function with. Keep working up with longer and longer lists, until you figure out what's going on.
Good luck!

Related

Local Procedure Bindings

I'm new to Scheme and Lisp in general, and upon learning I've stumbled upon a cryptic syntax used in local procedure binding:
(define mock
(lambda (s)
;; this is what I don't understand
(let splice ([l '()] [m (car s)] [r (cdr s)])
(append
(map (lambda (x) (cons m x)) r)
(if (null? r) '()
(splice (cons m l) (car r) (cdr r)))))))
It took me a while to grasp that splice is a scoped procedure with 3 arities. Rewriting this in an ML-esque style it seems to produce similar output:
(define mock2
(lambda (s)
;; define `splice` first
(define splice
(lambda (la lb lc)
(append
(map (lambda (x) (cons lb x)) lc)
(if (null? lc) '()
(splice (cons lb la) (car lc) (cdr lc))))))
;; bind `splice` and its arguments together and call it with them
(let ([sp splice] [l '()] [m (car s)] [r (cdr s)])
(splice l m r))))
The second version is a bit longer and somewhat look more imperative, but defining splice as a normal procedure inside the scope before binding it in parallel with the arguments (or just chuck them in as-is) and calling it looks saner.
Question is are these two versions replaceable? If yes, could you help explain the first version's syntax of binding local variables (l, m, and r) within the splice binding form?
Calling splice is like re-entering a loop, which is what it is there for. A tail call is a goto anyway. It is often named loop, instead of thinking up some special name for it.
"looks saner" is debatable, and actually with Schemers you'll lose this one, because this is a very popular Scheme construct, called "named let". It is usually re-written with letrec btw, if/when one wants to rewrite it, to understand it better. Internal define can be used as well, but then, why not use (define (mock s) ... in the first place.
So, the usual way to re-write this
(define mock ; or: (define (mock s) ...
(lambda (s)
(let splice ([l '()] [m (car s)] [r (cdr s)])
(append
(map (lambda (x) (cons m x)) r)
(if (null? r) '()
(splice (cons m l) (car r) (cdr r)))))))
is this:
(define mock
(lambda (s)
(letrec ([splice (lambda (l m r) ; or: (define (splice l m r) ...
(append
(map (lambda (x) (cons m x)) r)
(if (null? r) '()
(splice (cons m l) (car r) (cdr r)))))])
(splice '() (car s) (cdr s)))))
and writing it in the named let way saves one from having it defined in one place and called in another, potentially far away. A call enters its body from the start anyway, and named let better reflects that.
This is pretty self-explanatory. The transformation from one form to the other is purely syntactical, and both can be used interchangeably.

Depth-cyclic list

(define depth-count
(lambda (l)
(let ((visited '())
(counter 0))
(let iter ((l l))
(cond ((pair? l)
(if (memq l visited)
(set! counter (+ 1 counter))
(begin
(set! visited (cons l visited))
(iter (car l))
(iter (cdr l)))))
(else '()))) counter)))
Imho, that else branch is unnecessary or just wrong, however that code seems to work, but I am not sure..
When I have .. let's say
(define l0 '(a b c))
(set-car! l0 l0)
(set-car! (cdr l0) l0)
(depth-count l0)
It should return 2, right? Is is correct then?
You are correct that the expression (else '()) is superfluous. It means that your cond expression will sometimes evaluate to the empty list. Hence, your inner let will sometimes evaluate to the empty list.
It is superfluous because you are not using the result of the inner let for anything. The result is discarded by the outer let, which returns the value of its final sub-expression: counter.
Yes, 2 is a reasonable (predictable) result for the input you've suggested.
As for its correctness, you really need to state more clearly what you are trying to achieve. The 'depth' of a 'cyclic list' is not a well-defined concept.

Is this scheme code tail recursive?

EDIT: Thanks to everyone. I'm new to the language(just started using it two days ago), so that's why I'm unfamiliar with conds. I may rewrite it if I have time, but I just wanted to make sure I had the basic logic right. Thanks again!
My assignment is to make a tail-recursive function that removes the nth element from the list, 1 <= n <= listlength, with only two parameters, list x and element n. So, (remove 1 '(a b c d)) will return (b c d). I have written the following, and would like some reassurance that it is indeed tail recursive. The only thing I'm fuzzy on is if the recursive call can be nested inside an IF statement.
(define (remove n x)
; if n is 1, just return the cdr
(if (and (not (list? (car x))) (= n 1))
(cdr x)
; if the car is not a list, make it one, and call recursively
(if (not (list? (car x)))
(remove (- n 1) (cons (list (car x)) (cdr x)))
; if n !=1, insert the cadr into the list at the car.
; Else, append the list at the car with the cddr
(if (not(= n 1))
(remove (- n 1) (cons (append (car x) (list(cadr x))) (cddr x)))
(append (car x) (cddr x))))))
Yes, the procedure is tail-recursive, meaning: wherever a recursive call is performed, it's the last thing that happens in that particular branch of execution, with nothing more to do after the recursion returns - hence, we say that the recursive call is in tail position.
This can be clearly seen if we rewrite the procedure using cond instead of nested ifs, here you'll see that every branch of execution leads to either a base case or a recursive case, and all recursive calls are in tail position:
(define (remove n x)
; base case #1
(cond ((and (not (list? (car x))) (= n 1))
; return from base case, it's not recursive
(cdr x))
; recursive case #1
((not (list? (car x)))
; recursive call is in tail position
(remove (- n 1) (cons (list (car x)) (cdr x))))
; recursive case #2
((not (= n 1))
; recursive call is in tail position
(remove (- n 1) (cons (append (car x) (list(cadr x))) (cddr x))))
; base case #2
(else
; return from base case, it's not recursive
(append (car x) (cddr x)))))
For a more technical explanation of why the consequent/alternative parts of an if special form can be considered tail-recursive, take a look at section 3.5 of the current draft of the Revised^7 Report on the Algorithmic Language Scheme - the language specification, here's a link to the pdf file (in essence the same considerations apply to R5RS, it's just that they're explained in more detail in R7RS). In particular:
If one of the following expressions is in a tail context, then the subexpressions shown as ⟨tail expression⟩ are in a tail context
...
(if ⟨expression⟩ ⟨tail expression⟩ ⟨tail expression⟩)
(if ⟨expression⟩ ⟨tail expression⟩)
Here is the Scheme specification on the tail recursion position for syntactic forms:

implement expand function with racket

I can't seem to figure out how to write this function. What I am trying to write is a function expand that takes a list lst as a parameter of the form '(a (2 b) (3 c)) and is evaluated to '(a b b c c c)
This looks like homework, so I'm not giving you a straight answer. Instead, I'll give you some pointers in the right direction. The most useful hint, is that you should split the problem in two procedures, one for processing the "outer" list and the other for generating the repetitions encoded in the inner sublists.
Notice that both procedures are mutually recursive (e.g., they call each other). The expand procedure recurs over the list, whereas the repeat procedure recurs over the number of repetitions. This is the general structure of the proposed solution, fill-in the blanks:
; input: lst - list to be processed
; output: list in the format requested
(define (expand lst)
(cond ((null? lst) ; if the list is null
'()) ; then return null
((not (pair? (car lst))) ; if the first element of the list is an atom
(cons <???> <???>)) ; cons the atom and advance the recursion
(else ; if the first element of the list is a list
<???>))) ; call `repeat` with the right params
; input: n - number of repetitions for the first element in the list
; lst - list, its first element is of the form (number atom)
; output: n repetitions of the atom in the first element of lst
(define (repeat n lst)
(if (zero? n) ; if the number of repetitions is zero
(expand (cdr lst)) ; continue with expand's recursion
(cons <???> ; else cons the atom in the first element and
<???>))) ; advance the recursion with one less repetition
As this was answered three years ago, I don't think that I am helping with homework. Would just like to point out that the two functions really don't need to be mutually recursive. As replicate is a fairly common function, I would propose:
(define (replicate what n)
(if (zero? n)
(list)
(cons what (replicate what (- n 1)))))
(define (my-expand xs)
(if (empty? xs)
(list)
(let ((x (first xs)))
(if (list? x)
(let ((the-number (first x))
(the-symbol (cadr x)))
(flatten (cons (replicate the-symbol the-number)
(my-expand (rest xs)))))
(cons x (my-expand (rest xs)))))))
Of course it is better to use two lists and perform the flatten at the end, something like this:
(define (my-expand xs)
(define (inner-expander xs ys)
(if (empty? xs) (flatten (reverse ys))
(let ((x (first xs)))
(if (list? x)
(let ((the-number (first x))
(the-symbol (cadr x)))
(inner-expander (rest xs) (cons (replicate the-symbol the-number) ys)))
(inner-expander (rest xs) (cons x ys))))))
(inner-expander xs (list)))

Scheme append procedure

I'm having trouble appending a list to another list. Below is my code. When I run (append '(1 2) '(3 4)) I get '(1 3 2 4).
I want the output to be '(1 2 3 4)
(define (append l m)
(if (null? l) '()
(cons (car l) (append m (cdr l)))))
Thanks
Well by switching the two lists around like that (switching the position of m and l when calling append recursively), you'll get the first item of the first list followed by the first item of the second list etc.
If you don't want that, you should keep l as the first argument and m as the second. So you get:
(define (append l m)
(if (null? l) '()
(cons (car l) (append (cdr l) m))))
Of course this doesn't work as wanted either, because now you only get the first list back and nothing is appended at all. What you need to do is, once the first list is fully appended (i.e. once l is empty), you need to return the second one as the tail, like this:
(define (append l m)
(if (null? l) m
(cons (car l) (append (cdr l) m))))
I came across this while studying myself. #sepp2k's answer is a fine piece of instruction guiding OP to correct their code to achieve a recursive definition of append. Here's an alternate definition of my-append using the higher-order function foldr:
(define (myappend xs ys)
(foldr cons ys xs))

Resources