Terry Chung
Terry Chung

Reputation: 27

Let vs Local define in racket

I was working on this bit of code

  (letrec ([f (lambda (x)
                (if (= (remainder x 5) 0)
                    (cons (- x) (lambda () (f (+ x 1))))
                    (cons x (lambda () (f (+ x 1))))))])
    (lambda () (f 1))))

And I thought, why use a letrec and an anoymous function? Could I not use a local and a define - something like this?

(local [(define (f x) (if....]))

Is there any particular reason to use anymous functions instead of local-defines?

Upvotes: 0

Views: 1055

Answers (2)

Sylwester
Sylwester

Reputation: 48745

Everytime you see (define (something ...) ..) it is rewritten (define something (lambda (...) ...)) by the language. It is just syntax sugar to make it easier to write procedures.

local is not inherited by Scheme. In fact it is more specific syntax to do the same as just placing define in the procedure body:

;;; standard Scheme letrec
(define (reverse lst)
  (letrec ([helper (lamda (lst acc) 
                     (if (null lst) 
                         acc 
                         (helper (cdr lst) (cons (car lst) acc))))])
    (helper lst '()))


;;; Standard scheme local define
(define (reverse lst)
  (define (helper (lst acc) 
    (if (null lst) 
        acc 
        (helper (cdr lst) (cons (car lst) acc))))
  (helper lst '()))

;;; Implementation specific Racket way
(define (reverse lst)
  (local [(define (helper (lst acc) 
            (if (null lst) 
                acc 
               (helper (cdr lst) (cons (car lst) acc))))]
    (helper lst '()))

All these are equal. In fact the standard Scheme report explains that the local define can be implemented by rewriting it to a letrec or vice versa.

The local version is unique to Racket language and is not a part of Scheme. Eg. if you are writing code that should work across implementations this cannot be used. As for your question since standard Scheme has both define and letrec in its base language they are the same thing.

That let and friends doesn't have a sugar for procedures are just unluck. By the time Scheme was made they probably didn't have the short form yet and when they made it there was no way to mend let and friends. If you don't like lambda use define (or local)

Upvotes: 3

Sorawee Porncharoenwase
Sorawee Porncharoenwase

Reputation: 6502

The full answer to your question is going to be somewhat complicated, because it involves multiple languages and their histories.

But in short, you can use local instead, nothing is wrong with that. You can also use letrec. Arguably, it's just a stylistic choice which one you prefer to use.


In the beginning, there's a language Scheme. Racket was a Scheme implementation, before it becomes its own language. Then, there are student languages for HtDP in Racket. These three languages are similar, but subtly different in many ways.

I assume that you are using the student languages from HtDP, which have the restriction that the body of functions must consist of exactly one "thing". For example, the following is invalid, because the body has two "things" in it.

(define (f x)
  1
  2)

This is why local is useful: it allows you to define local definitions, while itself counts as only one "thing".

(define (compute-fact-x-plus-one x)
  (local [(define (fact n)
            (if (zero? n)
                1 
                (* n (fact (sub1 n)))))]
    (add1 (fact x))))

In the real Racket language, there is no such restriction. You are able to write:

(define (compute-fact-x-plus-one x)
  (define (fact n)
    (if (zero? n)
        1 
        (* n (fact (sub1 n)))))
  (add1 (fact x)))

Therefore, local is not as useful in the real Racket language. In fact, it's not provided by the language by default. You need to specifically import it from an extra library, and I think most people don't even know that it exists!

local doesn't exist in Scheme. People would prefer to use letrec instead. So in old Racket code (when it was just a Scheme implementation), you will see that people use letrec a lot too. In modern Racket, letrec is less frequently used, because it's more verbose than a series of defines.

But that's not all. If we look at the implementation of languages, all series of defines (in modern Racket) and local (in student languages) are eventually converted to letrec internally.

In summary:

  • local is in practice only popular in HtDP student languages
  • You can just write a series of define directly in modern Racket code.
  • letrec is traditionally used a lot, but it's less frequently used directly in modern Racket. It's still used all the time as the internal target construct that local and a series of defines are converted to.

I could actually write more on this topic.

  • Scheme has multiple versions, and the behaviors of define across versions are different. And even in the same version, the behaviors of define across implementations are also different.
  • letrec can be seen as "superior" to a series of define because you can control the scope of variables it introduces.
  • etc, etc.

But I think this answer is long enough already.

Upvotes: 3

Related Questions