Search code examples
schemeracketcounterstateconceptual

Name of this state representation idea without local variables


I know this is maybe an oddball idea, but I thought might as well give it a try to ask here.

I was experimenting in Racket about state representation without local variables. The idea was defining a function that prints it's parameter value and if called again gives me another value. Since pure functions called with the same parameter always produce the same result, my workaround-idea got me the following.

(define (counter n)
  (displayln n)
  (λ () (counter (add1 n)))) ; unapplied lambda so it doesn't go in a loop

Then I devised a function to call counter and its resulting lambdas a certain number of times.

(define (call proc n)
  (unless (zero? n)
    (let ([x (proc)])
      (call x (sub1 n)))))

Which results in this:

> (call (counter 0) 5)
0
1
2
3
4
5

What is the name for the concept applied here? Propably it's something trivial what you need in real applications all the time, but since I have no experience in that respect yet so I can't pinpoint a name for it. Or maybe I just complicated something very simple, but nonetheless I would appreciate an answer so I can look further into it.

Sorry if my question is not clear enough, but english is not my first language and to ask about things I have no name for makes me feel kinda uncertain.


Solution

  • You're using closures to save state: a lambda form stores the environment in which it was defined, and you keep redefining the procedure (called x in your code), so each time it "remembers" a new value for n.

    Another way to do the same would be to let the procedure itself keep track of the value - in other words, counter should remember the current n value between invocations. This is what I mean:

    (define (counter initial)
      (let ((n (sub1 initial)))
        (lambda ()
          (set! n (add1 n))
          n)))
    

    In the above code, the first invocation of counter returns a new lambda that closes over n, and each invocation of that lambda modifies n and returns its new value. Equivalently, we could use Racket-specific syntax for currying and the begin0 special form to achieve the same effect:

    (define ((counter n))
      (begin0
        n
        (set! n (add1 n))))
    

    Either way, notice how the procedure "remembers" its previous value:

    (define proc (counter 0))
    (proc)
    => 0
    (proc)
    => 1
    

    And we would call it like this:

    (define (call proc n)
      (unless (zero? n)
        (displayln (proc))
        (call proc (sub1 n))))
    
    (call (counter 0) 5)
    => 0
       1
       2
       3
       4
    

    Also notice that the above fixes an off-by-one error originally in your code - the procedure was being called six times (from 0 to 5) and not five times as intended, that happened because call invokes counter five times, but you called counter one more time outside, when evaluating (counter 0).