Reputation: 231
(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
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
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