Aditya Agarwal
Aditya Agarwal

Reputation: 231

Cannot understand output in the second case

(define c
  (let ((d 10))
    (set! d (- d 2))
    d))
(define c1
  (let ((d 10))
   (lambda (p)
     (set! d (- d p))
     d)))

Now for c c the output is 8 8.

But for (c1 2) (c1 2) the output is 8 6. Why is that so?

I think I need an insight to how function calls are actually evaluated.

According to me, the evaluation should be as, (in the second case) for the first call, a local environment for function c1 is created where the value of d is 10 and then the procedure evaluation takes place normally. Then as soon as this call ends the whole environment is destroyed and for the second call the same whole process (as above) occurs. So the second output value should also be 8. But it is 6, why is that so?

Upvotes: 1

Views: 582

Answers (2)

Atharva Shukla
Atharva Shukla

Reputation: 2137

A let can be rewritten as an immediate application of a lambda abstraction

(mylet ([var rhs] ...) body ...) => ((lambda (var ...) body ...) rhs ...)

De-sugaring c's let yields

(define c ((lambda (d) (set! d (- d 2)) d) 10))

Which is just an application of 10 onto a function (which we call f)

(define f (lambda (d) (set! d (- d 2)) d))
(define c 
  (f 10))
c
c

As for c1, we have nested lambdas

(define f1 (lambda (d) (lambda (p) (set! d (- d p)) d)))
((f1 10) 2)
((f1 10) 2)

8 and 8. (Which is what you expected). But really, what's happening is

(define c1
  (f1 10))
(c1 2)
(c1 2)

returns 8 and 6

The d gets memoized (here is an example of fibonacci using the same wrapping of a memoize procedure and set!).

Moreover, for set!, you can't have naive substitution. The racket's evaluation model explains how "a new location is created for each variable on each application":

Since the value associated with argument variable x can be changed, the value cannot be substituted for x when the procedure is first applied.

tl;dr evaluation of c1 yields a lambda that closes over pending substitution (environment). Which is then mutated by set! on each call.

Upvotes: 0

Sylwester
Sylwester

Reputation: 48745

Are you thinking this:

(define c1
  (let ((d 10))
    (lambda (p)
      (set! d (- d p))
      d)))

It is exactly the same as:

(define c1
  (lambda (p)
    (let ((d 10))
      (set! d (- d p))
      d)))

It is not. In the first the variable d is created before the lambda and thus it is the same free variable for each and every invocation of c1. Thus changing d alters next call.

The second one creates d at invocation and it gets destroyed when the call is finished.

In the first Scheme evaluates the let form. It creates d and then evaluates the lambda so d becomes a free variable in its closure that is returned. The define syntax then creates a global variable c1 with that resulting closure value. The let is out of scope, but d doesn't get garbage collected since it is still referenced by one value, the closure.

Upvotes: 1

Related Questions