1

This is from the SICP course for the environmental model section:

(define make-counter 
   (lambda (n)    
     (lambda () (set! n (+ n 1))
       n)))    

Below,the interpreter says that (make-counter 0) is a procedure:

> (make-counter 0)
#<procedure:...make_counter.rkt:8:5>

Below, I define c1 to be (make-counter 0).

(define c1 (make-counter 0)

Below is where I get confused as to the reason (c1) returns the counter values instead of "procedure".

>(c1)
1
> (c1)
2
> (c1)
3

My thinking process is that if (c1) points to a procedure, (make-counter), then (c1) should return "procedure:...make_counter.rkt:8:5".

Because procedure -> procedure.

I see what should happen, I am just confused, conceptually, as to how and why.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • These answers are helping me check my thinking. One piece of the puzzle is how does – Trieu Nguyen Apr 13 '16 at 16:29
  • Please do not forget: upvote helpful answers, and mark one as solution so this is closed. Or reformulate the question. – Str. Apr 13 '16 at 16:43
  • Finally, I'm clear of why (c1) returns the counter. Now, its (make-counter 0). Working it by hand, I am not understanding why n is not returned. Can someone explain this to me? – Trieu Nguyen Apr 13 '16 at 16:59

3 Answers3

2

What is your question? Do you doubt it works like the name suggests, or do you not understand how it does?

The first can be tested in 30 seconds, I'll answer the second:

make-counter is a procedure that takes one argument. Now look at the code again: what does it return? A procedure with 0 arguments.

So executing (c1) will return the numbers from 1 on upwards (if starting with 0).

For completeness:

Gambit v4.8.1

> (define make-counter 
   (lambda (n)    
     (lambda () (set! n (+ n 1))
       n)))    
> (define c1 (make-counter 0))
> (c1)
1
> (c1)
2
> (c1)
3
> 

Addition after question edit:

c1 is the procedure, but (c1) is procedure application, what you would call "calling the procedure" in another programming world.

> c1
#<procedure #2 c1>
> (c1)
1

BTW a nice piece of functional code, if you have understood this you will look at programming differently.

More questions via comment:

Now, its (make-counter 0). Working it by hand, I am not understanding why n is not returned.

The answer is the same we gave you about c1 and (c1):

make-counter returns lambda (), and n is only returned if you call the lambda. Functions (in scheme: procedures, lambdas) are values that can be handed around, functional programming principle. You have to call them (correct term: apply) the get the procedure result.

Now tell me: how do you call a procedure in scheme?

more edits:

Okay, we call a procedure in scheme by enclosing it in parens.

Now tell me: What is the substitution step for (c1) ?

Str.
  • 1,389
  • 9
  • 14
  • Yes, its starting to make sense to my thick skull! To call a function: Enclosing the function in parenthesis with the required arguments. This will evaluate the procedure and apply the arguments to it. In this case, since it is the environment model, it doesnt just use the substitution rule completely, right? It seems to need the extra step in with the inside lambda encapsulates the n variable, which causes it to be unbound.(make-counter 0) evaluates and applies 0 to n but returns procedure() and not new n value. I have to (c1 (make-counter 0)) to return the the new n. Is this right? – Trieu Nguyen Apr 14 '16 at 14:44
  • @TrieuNguyen About application: right, but second part false, c1 is the lambda that does not take parameters. The point is c1 is executed in the environment of make-counter, so variable n occurs free in c1. Maybe you should let this question rest and mature for some days, then rethink it. – Str. Apr 14 '16 at 15:30
  • Im going to take you advice and let it rest for a few days then I'll get back to it. – Trieu Nguyen Apr 15 '16 at 15:51
  • This is after a few days of letting it rest. Here are some observations. I can see the first problem is in personal error in syntax. Second error would be semantic error in the language of scheme. I "think" I learned from this after rechecking your answers. It seems to me that I want to go to the next step and understand the general way the evaluator applies this type of procedure and that can lead me the rest of the way on the never ending journey of functional programming. – Trieu Nguyen Apr 18 '16 at 16:48
  • In the error of semantic error, An "aha" moment happened this weekend when this gentleman asked a very basic question in lecture 7a @ 53:25 and the following answer by the professor. (lecture 7a from MIT 6.001 SICP) – Trieu Nguyen Apr 18 '16 at 16:58
  • @string. Ok, after looking at the lecture and thinking that I had an "aha" moment. I realized that after messing with the code I was still all wrong somewhere fundamentally. So, I reviewed the fundamentals. Can you correct me if I am wrong? – Trieu Nguyen Apr 24 '16 at 17:07
  • Here it is : (make-counter 0) -> procedure, not 1. ((make-counter 0)) -> 1 because basically bc what you told me. This should fit into the reason why (c1) works and basically how scheme expresses closure and hence this counter. Is this the correct thinking? – Trieu Nguyen Apr 24 '16 at 17:08
  • @TrieuNguyen sounds right. But I feel it is a little ..hm. convoluted, like you are making it more complex than it is. – Str. Apr 24 '16 at 19:12
  • Yes, but the backend insight and extra knowledge which I gained in this process was well worth it. It helped me peel some mystery behind compilers, the metacircular evaluator. The knowing why something works. In this thought, the question is finally answered. Thank you so much for your help, Str. – Trieu Nguyen Apr 29 '16 at 05:24
2

My thinking process is that if (c1) points to a procedure, (make-counter), then (c1) should return "procedure:...make_counter.rkt:8:5".

This is wrong.

What is happening here is that c1 “points to a procedure”, as you say, (really it is a name bound to a procedure).

You should remember that in Scheme the form (f e1 e2 ... en) is the way to call the procedure f, passing to it the values of the expressions e1 e2 ... en.

So, (c1) is completely different from c1: it is a way of calling the procedure c1 (that has no parameter). And each time the interpreter/compiler evaluates (c1), it calls the procedure, that increases the value and return it.

Renzo
  • 26,848
  • 5
  • 49
  • 61
1

You have

(define make-counter 
   (lambda (n)    
     (lambda () (set! n (+ n 1))
       n)))  

so evaluating make-counter

returns

   (lambda (n)    
     (lambda () (set! n (+ n 1))
       n))

and evaluating (make-counter 0) just replaces make-counter in that call with its value, and proceeds as

( (lambda (n)    
     (lambda () (set! n (+ n 1))
       n))
   0 )
=>
(let ((n 0))    
     (lambda () (set! n (+ n 1)) 
       n))

so the closure object is created and returned,

=>
(closure {environment1: ((n 0))}     ; its own binding for `n`
     (lambda () (set! n (+ n 1)) 
       n))

so after (define c1 (make-counter 0)) this closure is the value of c1.

Evaluating c1 returns its value, the procedure (closure) above. Its printed representation is implementation-dependent (showing e.g. #<procedure> or something similar).

Evaluating (c1) calls this procedure,

(c1)
=>
( (closure {environment1: ((n 0))}     ; its own binding for `n`
     (lambda () (set! n (+ n 1)) 
       n)) )
=>
(under {environment1: ((n 0))}
  (  (lambda () (set! n (+ n 1)) 
       n)) )

Calling ((lambda () ...) ) without any arguments just evaluates its body without creating any new environment, since the parameters list is empty and there are no arguments:

=>
(under {environment1: ((n 0))}
    (set! n (+ n 1))             ; perform this first
    n )
=>
(under {environment1: ((n 1))}   ; <--- altered binding for `n`!
    n )                          ; now evaluate this and return its value
=>
1

and it leaves the altered environment1 in its wake. When that procedure (closure) will be called again, (c1), its environment will now contain ((n 1)), and so will be changed to ((n 2)), etc.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Explaining the extra rules really helps my understanding/anxiety lol. Its still using the substitution rule but since it creates an environment the interpreter has to do a few extra steps- except I do not have to explicitly write this. Is this correct? There is a little more that goes under the hood (as you have explained). Would this fall into the category of a special form or syntactic sugar in a sense that we dont really have to explicitly run the lines of code, instead I just need to define make-counter and c1? – Trieu Nguyen Apr 15 '16 at 16:14
  • It helps to imagine that Scheme really does create real objects in memory, built of pairs and symbols - i.e. the symbolic expressions -- see http://www-formal.stanford.edu/jmc/recursive.ps. Studying this paper (and the mceval chapter of SICP) really helped me. See also if [this answer of mine](http://stackoverflow.com/a/34910496/849891) clarifies things (if not, don't put too much effort into it, go with the classics). -- So this evaluation process is very concrete and explicit, it actually manipulates those tagged lists in memory. When this is understood, we can move on to more sophisticated – Will Ness Apr 15 '16 at 16:55
  • ... modes of interpretation/compilation. -- no, there's no special forms there. When I write `(under {environment1: ...` it's just like a pseudocode. But it can be implemented as actual tagged lists in memory (and "environment1" is a name of such object in memory, with a specific memory address - a list of variable names and their values, i.e. the environment). See my other answer that I mentioned, which rewrites the code from that paper in some more readable (for me) pseudocode. (correction: code from the book mentioned there, not paper; but they are close). Do study the code from the paper. – Will Ness Apr 15 '16 at 17:01
  • there's also this [paper by Wadler](https://www.cs.kent.ac.uk/people/staff/dat/miranda/wadler87.pdf) with a critique of Lisp and praises for SASL/Miranda/Haskell syntax. It's good to see stuff from several viewpoints. :) – Will Ness Apr 15 '16 at 17:10
  • You said it helps to imagine how Scheme does this. I think this hereby lies my ultimate disconnect - after correcting my semantic and syntactic issues. I'm going look over the papers you gave me with the intent of helping me see this process more. I'll get back to you in a few days. – Trieu Nguyen Apr 18 '16 at 16:53
  • @WillNess and Wadler is right also in OCaml syntax. – Str. Apr 18 '16 at 19:24
  • 1
    @WillNess. Wow man this is great- these papers and links you gave me are really dense reading material (at least for me). In particular, "Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I". I'll have to take it in bite size chunks but worth getting familiar with it. Thank you for the resources since it goes in detail the mathematics behind this and other concepts as well!!! – Trieu Nguyen May 02 '16 at 16:27
  • Human logic is a tricky business--we can think we've proved something but actually be wrong. How to be sure? Take a human out of the picture. Make a Machine manipulate "Symbolic Expressions" (a "symbol" (name, basically), or a pair of "Symbolic Expressions") purely mechanically, by itself. So, McCarthy had described such a simple enough algorithm to transform--simplify--such S.E.s mechanically. So that `'((lambda (x) (+ x x)) 3)` is a *datum*--a symbolic expression--but if manipulated by said algorithm, is "evaluated" to just `6`--becomes *code*. So we can be sure 3+3==6; the Machine told us. – Will Ness May 02 '16 at 21:20