Space complexity of streams in Scheme - functional-programming

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.

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.

Recursion in Common Lisp, pushing values, and the Fibonacci Sequence

This is not a homework assignment. In the following code:
(defparameter nums '())
(defun fib (number)
(if (< number 2)
number
(push (+ (fib (- number 1)) (fib (- number 2))) nums))
return nums)
(format t "~a " (fib 100))
Since I am quite inexperienced with Common Lisp, I am at a loss as to why the function does not return an value. I am a trying to print first 'n' values, e.g., 100, of the Fibonacci Sequence.
Thank you.
An obvious approach to computing fibonacci numbers is this:
(defun fib (n)
(if (< n 2)
n
(+ (fib (- n 1)) (fib (- n 2)))))
(defun fibs (n)
(loop for i from 1 below n
collect (fib i)))
A little thought should tell you why no approach like this is going to help you compute the first 100 Fibonacci numbers: the time taken to compute (fib n) is equal to or a little more than the time taken to compute (fib (- n 1)) plus the time taken to compute (fib (- n 2)): this is exponential (see this stack overflow answer).
A good solution to this is memoization: the calculation of (fib n) repeats subcalculations a huge number of times, and if we can just remember the answer we computed last time we can avoid doing so again.
(An earlier version of this answer has an overcomplex macro here: something like that may be useful in general but is not needed here.)
Here is how you can memoize fib:
(defun fib (n)
(check-type n (integer 0) "natural number")
(let ((so-far '((2 . 1) (1 . 1) (0 . 0))))
(labels ((fibber (m)
(when (> m (car (first so-far)))
(push (cons m (+ (fibber (- m 1))
(fibber (- m 2))))
so-far))
(cdr (assoc m so-far))))
(fibber n))))
This keeps a table – an alist – of the results it has computed so far, and uses this to avoid recomputation.
With this memoized version of the function:
> (time (fib 1000))
Timing the evaluation of (fib 1000)
User time = 0.000
System time = 0.000
Elapsed time = 0.000
Allocation = 101944 bytes
0 Page faults
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875
The above definition uses a fresh cache for each call to fib: this is fine, because the local function, fibber does reuse the cache. But you can do better than this by putting the cache outside the function altogether:
(defmacro define-function (name expression)
;; Install EXPRESSION as the function value of NAME, returning NAME
;; This is just to avoid having to say `(setf ...)`: it should
;; probably do something at compile-time too so the compiler knows
;; the function will be defined.
`(progn
(setf (fdefinition ',name) ,expression)
',name))
(define-function fib
(let ((so-far '((2 . 1) (1 . 1) (0 . 0))))
(lambda (n)
(block fib
(check-type n (integer 0) "natural number")
(labels ((fibber (m)
(when (> m (car (first so-far)))
(push (cons m (+ (fibber (- m 1))
(fibber (- m 2))))
so-far))
(cdr (assoc m so-far))))
(fibber n))))))
This version of fib will share its cache between calls, which means it is a little faster, allocates a little less memory but may be less thread-safe:
> (time (fib 1000))
[...]
Allocation = 96072 bytes
[...]
> (time (fib 1000))
[...]
Allocation = 0 bytes
[...]
Interestingly memoization was invented (or at least named) by Donald Michie, who worked on breaking Tunny (and hence with Colossus), and who I also knew slightly: the history of computing is still pretty short!
Note that memoization is one of the times where you can end up fighting a battle with the compiler. In particular for a function like this:
(defun f (...)
...
;; no function bindings or notinline declarations of F here
...
(f ...)
...)
Then the compiler is allowed (but not required) to assume that the apparently recursive call to f is a recursive call into the function it is compiling, and thus to avoid a lot of the overhead of a full function call. In particular it is not required to retrieve the current function value of the symbol f: it can just call directly into the function itself.
What this means is that an attempt to write a function, memoize which can be used to mamoize an existing recursive function, as (setf (fdefinition 'f) (memoize #'f)) may not work: the function f still call directly into the unmemoized version of itself and won't notice that the function value of f has been changed.
This is in fact true even if the recursion is indirect in many cases: the compiler is allowed to assume that calls to a function g for which there is a definition in the same file are calls to the version defined in the file, and again avoid the overhead of a full call.
The way to deal with this is to add suitable notinline declarations: if a call is covered by a notinline declaration (which must be known to the compiler) then it must be made as a full call. From the spec:
A compiler is not free to ignore this declaration; calls to the specified functions must be implemented as out-of-line subroutine calls.
What this means is that, in order to memoize functions you have to add suitable notinline declarations for recursive calls, and this means that memoizing either needs to be done by a macro, or must rely on the user adding suitable declarations to the functions to be memoized.
This is only a problem because the CL compiler is allowed to be smart: almost always that's a good thing!
Your function unconditionally returns nums (but only if a variable called return exists). To see why, we can format it like this:
(defun fib (number)
(if (< number 2)
number
(push (+ (fib (- number 1)) (fib (- number 2))) nums))
return
nums)
If the number is less than 2, then it evaluates the expression number, uselessly, and throws away the result. Otherwise, it pushes the result of the (+ ....) expression onto the nums list. Then it uselessly evaluates return, throwing away the result. If a variable called return doesn't exist, that's an error situation. Otherwise, it evaluates nums and that is the return value.
In Common Lisp, there is a return operator for terminating and returning out of anonymous named blocks (blocks whose name is the symbol nil). If you define a named function with defun, then an invisible block exists which is not anonymous: it has the same name as that function. In that case, return-from can be used:
(defun function ()
(return-from function 42) ;; function terminates, returns 42
(print 'notreached)) ;; this never executes
Certain standard control flow and looping constructs establish a hidden anonymous block, so return can be used:
(dolist (x '(1 2 3))
(return 42)) ;; loop terminates, yields 42 as its result
If we use (return ...) but there is no enclosing anonymous block, that is an error.
The expression (return ...) is different from just return, which evaluates a variable named by the symbol return, retrieving its contents.
It is not clear how to repair your fib function, because the requirements are unknown. The side effect of pushing values into a global list normally doesn't belong inside a mathematical function like this, which should be pure (side-effect-free).
So you might know that if you know the two previous numbers you can compute the next. What comes after 3, 5? If you guess 8 you have understood it. Now if you start with 0, 1 and roll 1, 1, 1, 2, etc you collect the first variable until you have the number of numbers you'd like:
(defun fibs (elements)
"makes a list of elements fibonacci numbers starting with the first"
(loop :for a := 0 :then b
:for b := 1 :then c
:for c := (+ a b)
:for n :below elements
:collect a))
(fibs 10)
; ==> (0 1 1 2 3 5 8 13 21 34)
Every form in Common Lisp "returns" a value. You can say it evaluates to. eg.
(if (< a b)
5
10)
This evaluates either to 5 or 10. Thus you can do this and expect that it evaluates to either 15 or 20:
(+ 10
(if (< a b)
5
10))
You basically want your functions to have one expression that calculates the result. eg.
(defun fib (n)
(if (zerop n)
n
(+ (fib (1- n)) (fib (- n 2)))))
This evaluates to the result og the if expression... loop with :collect returns the list. You also have (return expression) and (return-from name expression) but they are usually unnecessary.
Your global variable num is actually not that a bad idea.
It is about to have a central memory about which fibonacci numbers were already calculated. And not to calculate those already calculated numbers again.
This is the very idea of memoization.
But first, I do it in bad manner with a global variable.
Bad version with global variable *fibonacci*
(defparameter *fibonacci* '(1 1))
(defun fib (number)
(let ((len (length *fibonacci*)))
(if (> len number)
(elt *fibonacci* (- len number 1)) ;; already in *fibonacci*
(labels ((add-fibs (n-times)
(push (+ (car *fibonacci*)
(cadr *fibonacci*))
*fibonacci*)
(cond ((zerop n-times) (car *fibonacci*))
(t (add-fibs (1- n-times))))))
(add-fibs (- number len))))))
;;> (fib 10)
;; 89
;;> *fibonacci*
;; (89 55 34 21 13 8 5 3 2 1 1)
Good functional version (memoization)
In memoization, you hide the global *fibonacci* variable
into the environment of a lexical function (the memoized version of a function).
(defun memoize (fn)
(let ((cache (make-hash-table :test #'equal)))
#'(lambda (&rest args)
(multiple-value-bind (val win) (gethash args cache)
(if win
val
(setf (gethash args cache)
(apply fn args)))))))
(defun fib (num)
(cond ((zerop num) 1)
((= 1 num) 1)
(t (+ (fib (- num 1))
(fib (- num 2))))))
The previously global variable *fibonacci* is here actually the local variable cache of the memoize function - encapsulated/hidden from the global environment,
accessible/look-up-able only through the function fibm.
Applying memoization on fib (bad version!)
(defparameter fibm (memoize #'fib))
Since common lisp is a Lisp 2 (separated namespace between function and variable names) but we have here to assign the memoized function to a variable,
we have to use (funcall <variable-name-bearing-function> <args for memoized function>).
(funcall fibm 10) ;; 89
Or we define an additional
(defun fibm (num)
(funcall fibm num))
and can do
(fibm 10)
However, this saves/memoizes only the out calls e.g. here only the
Fibonacci value for 10. Although for that, Fibonacci numbers
for 9, 8, ..., 1 are calculated, too.
To make them saved, look the next section!
Applying memoization on fib (better version by #Sylwester - thank you!)
(setf (symbol-function 'fib) (memoize #'fib))
Now the original fib function is the memoized function,
so all fib-calls will be memoized.
In addition, you don't need funcall to call the memoized version,
but just do
(fib 10)

SICP - Imperative versus Functional implementation of factorial

I am studying the SICP book with Racket and Dr. Racket. I am also watching the lectures on:
https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/5a-assignment-state-and-side-effects/
At chapter 3, the authors present the concept of imperative programming.
Trying to illustrate the meaning, they contrast an implementation of a factorial procedure using functional programming and to one which used imperative programming.
Bellow you have a recursive definition of an iterative procedure using functional programming:
(define (factorial-iter n)
(define (iter n accu)
(if (= n 0)
accu
(iter (- n 1) (* accu n))))
; (trace iter)
(iter n 1))
Before the professor was going to present an imperative implementation, I tried myself.
I reached this code using the command "set!":
(define (factorial-imp n count product)
(set! product 1)
(set! count 1)
(define (iter count product)
(if (> count n)
product
(iter (add1 count) (* product count))))
(iter count product))
However, the professor's implementation is quite different of my imperative implementation:
(define (factorial-imp-sicp n)
(let ((count 1) (i 1))
(define (loop)
(cond ((> count n) i)
(else (set! i (* count i))
(set! count (add1 count))
(loop))))
(loop)))
Both codes, my implementation and the professor's code, reach the same results. But I am not sure if they have the same nature.
Hence, I started to ask myself: was my implementation really imperative? Just using "set!" guaranties that?
I still use parameters in my auxiliary iterative procedure while the professor's auxiliary iterative function does not have any argument at all. Is this the core thing to answer my question?
Thanks! SO users have been helping me a lot!
Your solution is splendidly mad, because it looks imperative, but it really isn't. (Some of what follows is Racket-specific, but not in any way that matters.)
Starting with your implementation:
(define (factorial-imp n count product)
(set! product 1)
(set! count 1)
(define (iter count product)
(if (> count n)
product
(iter (add1 count) (* product count))))
(iter count product))
Well, the only reason for the count and product arguments is to create bindings for those variables: the values of the arguments are never used. So let's do that explicitly with let, and I will bind them initially to an undefined object so it's clear the binding is never used (I have also renamed the arguments to the inner function so it is clear that these are different bindings):
(require racket/undefined)
(define (factorial-imp n)
(let ([product undefined]
[count undefined])
(set! product 1)
(set! count 1)
(define (iter c p)
(if (> c n)
p
(iter (add1 c) (* p c))))
(iter count product)))
OK, well, now it is obvious that any expression of the form (let ([x <y>]) (set! x <z>) ...) can immediately be replaced by (let ([x <z>]) ...) so long as whatever <y> is has no side effects and terminates. That is the case here, so we can rewrite the above as follows:
(define (factorial-imp n)
(let ([product 1]
[count 1])
(define (iter c p)
(if (> c n)
p
(iter (add1 c) (* p c))))
(iter count product)))
OK, so now we have something of the form (let ([x <y>]) (f x)): this can be trivially be replaced by (f <y>):
(define (factorial-imp n)
(define (iter c p)
(if (> c n)
p
(iter (add1 c) (* p c))))
(iter 1 1))
And now it is quite clear that your implementation is not, in fact, imperative in any useful way. It does mutate bindings, but it does so only once, and never uses the original binding before the mutation. This is essentially something which compiler writers call 'static single assignment' I think: each variable is assigned once and not used before it is assigned to.
PS: 'splendidly mad' was not meant as an insult, I hope it was not taken as such, I enjoyed answering this!
Using set! introduces side effects by changing a binding, however you change it from the passed value to 1 without using the passed value and you never change the value afterwards one might look at it as if 1 and 1 were constants passed to the helper like this:
(define (factorial-imp n ignored-1 ignored-2)
(define (iter count product)
(if (> count n)
product
(iter (add1 count) (* product count))))
(iter 1 1))
The helper updates it's count and product by recursing and thus is 100% functional.
If you were to do the same in an imperative language you would have made a variable outside a loop that you would update at each step in a loop, just like the professors implementation.
In your version you have altered the contract. The user needs to pass two arguments that are not used for anything. I've illustrated that by calling them ignored-1 and ignored-2.

is let working as a goto instruction in tail recursion?

I have found the following implementation of the Binary Search in Scheme:
(define (binary-search value vector)
(let helper ((low 0)
(high (- (vector-length vector) 1)))
(if (< high low)
#f
(let ((middle (quotient (+ low high) 2)))
(cond ((> (vector-ref vector middle) value)
(helper low (- middle 1)))
((< (vector-ref vector middle) value)
(helper (+ middle 1) high))
(else middle))))))
according to what it says in the comments, the above function uses tail-recursion to call to the help function. I was wondering if this works like a GOTO instruction, because I do not see that there is a proper "recursive" call to the binary-search function.
In this case it is proper to say that works like a goto instruction?
What you're seeing is called a named let. (I wrote a blog post about how named let works, if you're curious.) The code you have is exactly the same as:
(define (binary-search value vector)
(define (helper low high)
(if (< high low)
#f
(let ((middle (quotient (+ low high) 2)))
(cond ((> (vector-ref vector middle) value)
(helper low (- middle 1)))
((< (vector-ref vector middle) value)
(helper (+ middle 1) high))
(else middle)))))
(helper 0 (- (vector-length vector) 1)))
In other words, it is tail-recursing, on helper rather than on binary-search. But tail recursion is happening.
Some people think of tail-recursion like goto, but I don't consider that a helpful comparison. The only thing in common between the two is that you can implement loops with tail recursion, much like you can do with goto. But the similarities end there: tail-recursion is a special kind of recursion (where the current call frame is replaced with the tail call), but it's still recursion; goto jumps to an arbitrary point in the code, but it's a totally imperative operation with no relation to recursion.
A let is just syntactic sugar for lambdas. So for example:
(let ((i 2)
(j 5)
(* i j))
is equivalent to
((lambda (i j) (* i j)) 2 5)
The let in your code is called a named let, because you provided a label for it. So basically, your lambda in disguise is bound to a name. In that sense, it is a goto. However, you need to be in the scope of the let to be able to "jump" to it. The function is tail recursive because you are not deferring any computations. At any point in time, you only require the current values of i and j to be able to proceed with the computation.

Tail Recursive counting function in Scheme

The function is supposed to be tail-recursive and count from 1 to the specified number. I think I'm fairly close. Here's what I have:
(define (countup l)
(if (= 1 l)
(list l)
(list
(countup (- l 1))
l
)
)
)
However, this obviously returns a list with nested lists. I've attempted to use the append function instead of the second list to no avail. Any guidance?
Here's an incorrect solution:
(define (countup n)
(define (help i)
(if (<= i n)
(cons i (help (+ i 1)))
'()))
(help 1))
This solution:
uses a helper function
recurses over the numbers from 1 to n, cons-ing them onto an ever-growing list
Why is this wrong? It's not really tail-recursive, because it creates a big long line of cons calls which can't be evaluated immediately. This would cause a stack overflow for large enough values of n.
Here's a better way to approach this problem:
(define (countup n)
(define (help i nums)
(if (> i 0)
(help (- i 1)
(cons i nums))
nums)))
(help n '()))
Things to note:
this solution is better because the calls to cons can be evaluated immediately, so this function is a candidate for tail-recursion optimization (TCO), in which case stack space won't be a problem.
help recurses over the numbers backwards, thus avoiding the need to use append, which can be quite expensive
You should use an auxiliar function for implementing a tail-recursive solution for this problem (a "loop" function), and use an extra parameter for accumulating the answer. Something like this:
(define (countup n)
(loop n '()))
(define (loop i acc)
(if (zero? i)
acc
(loop (sub1 i) (cons i acc))))
Alternatively, you could use a named let. Either way, the solution is tail-recursive and a parameter is used for accumulating values, notice that the recursion advances backwards, starting at n and counting back to 0, consing each value in turn at the beginning of the list:
(define (countup n)
(let loop ((i n)
(acc '()))
(if (zero? i)
acc
(loop (sub1 i) (cons i acc)))))
Here a working version of your code that returns a list in the proper order (I replaced l by n):
(define (countup n)
(if (= 1 n)
(list n)
(append (countup (- n 1)) (list n))))
Sadly, there is a problem with this piece of code: it is not tail-recursive. The reason is that the recursive call to countup is not in a tail position. It is not in tail position because I'm doing an append of the result of (countup (- l 1)), so the tail call is append (or list when n = 1) and not countup. This means this piece of code is a normal recusrive function but to a tail-recursive function.
Check this link from Wikipedia for a better example on why it is not tail-recusrive.
To make it tail-recursive, you would need to have an accumulator responsible of accumulating the counted values. This way, you would be able to put the recursive function call in a tail position. See the difference in the link I gave you.
Don't hesitate to reply if you need further details.
Assuming this is for a learning exercise and you want this kind of behaviour:
(countup 5) => (list 1 2 3 4 5)
Here's a hint - in a tail-recursive function, the call in tail position should be to itself (unless it is the edge case).
Since countup doesn't take a list of numbers, you will need an accumulator function that takes a number and a list, and returns a list.
Here is a template:
;; countup : number -> (listof number)
(define (countup l)
;; countup-acc : number, (listof number) -> (listof number)
(define (countup-acc c ls)
(if ...
...
(countup-acc ... ...)))
(countup-acc l null))
In the inner call to countup-acc, you will need to alter the argument that is checked for in the edge case to get it closer to that edge case, and you will need to alter the other argument to get it closer to what you want to return in the end.

Resources