Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is let preferred to define in Scheme?

I've always written my Scheme procedures (and seen them written) like this:

(define (foo x)
  (let ((a ...))
       ((b ...))
    ...))

One of my students wrote:

(define (foo x)
  (define a ...)
  (define b ...)
  ...)

Both give the same results. I understand the difference in behavior: The first creates a new frame that points to the procedure application frame, while the latter modifies the procedure application frame directly. The latter would yield slightly better performance.

Another difference is that the former avoids using the implicit begin before a sequence of instructions in a procedure body.

Why is the former standard style?

like image 980
Ellen Spertus Avatar asked Feb 18 '14 21:02

Ellen Spertus


2 Answers

Actually, both styles are fine. In fact, some people prefer to use internal definitions.

Also, the latter doesn't necessarily "modify the procedure application frame directly", either; internal definitions are treated the same as a letrec (for R5RS-compliant systems) or letrec* (for R6RS- and R7RS-compliant systems). Thus, your second example is really the same as:

(define (foo x)
  (letrec* ((a ...)
            (b ...))
    ...))

In fact, to use an example, Racket rewrites internal definitions to their equivalent letrec* expressions and there is thus no difference in performance (beyond whatever difference there is between let and letrec*, of course).

like image 185
Chris Jester-Young Avatar answered Oct 20 '22 04:10

Chris Jester-Young


It's not completely equivalent. define within a procedure body is more like a letrec so you might get surprised you cannot use the value bound in a define before all of them are finished and the body of the procedure is to be executed. Imagine you want to do do x + y * z:

(define (test x y z)
  (let ((ytimesz (* y z)))
     (let ((sum (+ x ytimesz)))
       (dosomething sum))))

The reason you have a nested let here is because ytimesz can't be accessed in the same let as it is made. We have another special form for that let*

(define (test x y z)
  (let* ((ytimesz (* y z)) (sum (+ x ytimesz)))
       (dosomething sum)))

letrec and letrec* are similar but allows for recursion so in a lambda you can call one of the other bound member or itself. Now, depending on the version of Scheme you are using, you will get one of these when writing:

(define (test x y z)
  (define ytimesz (* y z))
  (define answer (+ x ytimesz)) ;might work, might not
  (dosomething answer))

In #!R7RS, #!R6RS and #!Racket it's perfectly ok since this are defined as letrec*.

In #!R5RS, however, it won't work at all. The rewrite is done as letrec and it initializes all variables (ytimesz and answer) to an undefined value, then assignes the evaluation of the expressions to temporary variables before set!-ing the variables to the values of the temporary values to make sure all use of any of them ends up as undefined values and some even signal errors (Racket does in R5RS-mode. For lambda-expressions where the bindings in the body are evaluated at call time, this is no problem and it's for these letrec and internal define is originally meant for.

I use define to store simple values and procedures. The second I think I need to use a precalculated value I might rewrite the whole thing to a let* or combine define and a simple let.

like image 34
Sylwester Avatar answered Oct 20 '22 03:10

Sylwester