i'm studying for my scheme final and objects with local state has always been a tough subject.
Here is a question from my final exam that i need help on.
(define (make-incrementer n)
(let ((old 0)
(new 0))
(lambda ()
(cond ((< new n)
(set! old new)
(set! new (+ new 1))
old)
(else
(set! old 0)
(set! new 1)
old)))))
(define a (make-incrementer 3))
(define b (make-incrementer 3))
(define c a)
; 1) (a)
; 2) (a)
why when a
is called the second time it returns 1? I'm looking at the code and the n
we give it is always 3. So wouldn't it always do the else
case?
Welcome to the wonderful world of closures! This is a textbook example of how closures in Scheme work.
So make-counter
returns a function that has 3 variables that it captures from it's enclosing environment: n
, old
, new
. In this case, the starting environment looks something like
_name_|_value_
n | 3
old | 0
new | 1
On each invocation, it increments old
and new
and wraps them around if they're greater than n
. Because it's using set!
, this incrementing is mutating the variables in the lambda's environment, but since these variables are captured from the surrounding environment, they are changed for all future calls as well.
That's why you get different returns even with the same input.
If this seems like witchcraft, you can think of it like objects in more common languages:
Eg Python:
class Foo():
def __init__(self, n, m):
self.n = n
self.m = m
def count(self):
self.n += 1
if self.n == self.m:
self.n = 1
return self.n-1
f = Foo(0, 2)
f.count() # 1
f.count() # 0
This is the same basic idea except here we're being a bit more explicit about where the environment is coming from, self
. In Scheme, we mimic this with the lambda capturing the surrounding variables.
For more, check out SICP