[ prog / sol / mona ]

prog


Internal definitions vs letrec

1 2020-08-01 20:53

Which one do you prefer?

(define (fibonacci n)
  (define (fibi n a b)
    (cond
     ((zero? n) b)
     (else (fibi (- n 1) (+ a b) a))))
  (fibi n 1 0))

(define (fibonacci n)
  (letrec ((fibi (lambda (n a b)
                   (cond
                    ((zero? n) b)
                    (else (fibi (- n 1) (+ a b) a))))))
    (fibi n 1 0)))
2 2020-08-01 21:25

I believe letrec is older than internal defines, although it does introduce unnecessary nesting. Here's a nicer way to write the latter:

(define (fibonacci n)
  (letrec ((fibonacci (lambda (n a b)
                        (cond
                         ((zero? n) b)
                         (else (fibi (- n 1) (+ a b) a))))))
    (fibonacci n 1 0)))
3 2020-08-01 21:25

I believe letrec is older than internal defines, although it does introduce unnecessary nesting. Here's a nicer way to write the latter:

(define (fibonacci n)
  (letrec ((fibonacci (lambda (n a b)
                        (cond
                         ((zero? n) b)
                         (else (fibonacci (- n 1) (+ a b) a))))))
    (fibonacci n 1 0)))
4 2020-08-01 22:08

>>1
I prefer named lets when they are applicable, and internal defines over letrec as in the following block:

(define (fibonacci n)
  (let fib-iter ((n n) (a 1) (b 0))
    (if (zero? n)
        b
        (fib-iter (- n 1) (+ a b) a))))
5 2020-08-01 22:13

>>4 is nice when it makes sense, but generally I'd say internal definitions (but that might just be my SICP bias speaking).

6 2020-08-02 12:33

>>5
I think there are good justifications for internal definitions, it's probably the reason that SICP uses them. For example they always create less indentation, text, and parenthesis. They also allow you to more easily test your internal abstractions at the top-level and generalize these abstractions if they turn out to be generally applicable. I find it very helpful to when defining a procedure to actually start with wishful thinking and define the necessary helper functions at the top-level and then to test incrementally as I'm writing these stand alone components. Only after having something that works do I consider moving these helper procedures into the primary procedure's body. One downside they you all probably remember form SICP is it is not specified in which order internal definitions will be bound.

7 2020-08-02 13:30

>>6
With libraries in R6RS and R7RS letting you selectively export bindings, is there still any reason to embed the helper functions? Maybe if you want to capture a variable from the parent function, but in that case you surely would use letrec instead of an internal definition, right?

8 2020-08-02 20:06

>>7
Well you did mention the two primary reasons of variable capture and avoiding namespace pollution. I don't see any reason for letrec to be used over internal definitions for variable capture, and I think avoiding namespace pollution remains desirable within a package. Reducing the number of procedures you have to keep up with when reasoning about a subset of a package is useful. If you find yourself in a position where the internal definitions are making things more difficult to reason about it's probably a decent hint that you should split off another package.

9 2020-08-05 05:58

>>8
I'm trying to rationalize my dislike for internal definitions.

10 2020-08-05 14:14 *

>>9
If there aren't any clear utilitarian advantages it might just be an aesthetic preference. There is a decent amount of diversity in aesthetic preference but aesthetics tend to bias towards things that are easier to process. Maybe for you letrec is just an easier form to process for you, that’s a perfectly valid reason to prefer the use of letrec.

11 2020-08-05 18:49 *

for completeness i wanted to see what the spec says (r5rs). e.g. common lisp's defun is processed in the lexical environment, but defines the function in the global environment, so

CL-USER> (defun foo ()
	   (defun bar () :result-of-bar)
	   :result-of-foo)
FOO
CL-USER> (ignore-errors (bar))
NIL
#<UNDEFINED-FUNCTION @ #x1000e64422>
CL-USER> (foo)
:RESULT-OF-FOO
CL-USER> (bar)
:RESULT-OF-BAR

i know that's not the case in scheme, but they do it by differentiating "at the top level of a ⟨program⟩ and at the beginning of a ⟨body⟩"

5.2.2. Internal definitions
Definitions may occur at the beginning of a ⟨body⟩ (that is, the body
of a lambda, let, let*, letrec, let-syntax, or letrec-syntax
expression or that of a definition of an appropriate form). Such
definitions are known as internal definitions as opposed to the top
level definitions described above. The variable defined by an internal
definition is local to the ⟨body⟩. That is, ⟨variable⟩ is bound rather
than assigned, and the region of the binding is the entire ⟨body⟩. For
example,

(let ((x 5))
  (define foo (lambda (y) (bar x y)))
  (define bar (lambda (a b) (+ (* a b) a)))
  (foo (+ x 3))) =⇒ 45

A ⟨body⟩ containing internal definitions can always be con- verted
into a completely equivalent letrec expression. For example, the let
expression in the above example is equiv- alent to

(let ((x 5))
  (letrec ((foo (lambda (y) (bar x y)))
           (bar (lambda (a b) (+ (* a b) a))))
    (foo (+ x 3))))

Just as for the equivalent letrec expression, it must be possible to
evaluate each ⟨expression⟩ of every internal def- inition in a ⟨body⟩
without assigning or referring to the value of any ⟨variable⟩ being
defined.

Wherever an internal definition may occur (begin ⟨definition1⟩ . . . )
is equivalent to the sequence of defini- tions that form the body of
the begin.
12 2023-08-12 02:49

I'm reading the recent Sussman/Hanson book 'Software Design for Flexibility' and IIRC it mentions that giving things names with internal DEFINEs makes them show up more clearly in the MIT Scheme debugger (which is very, very good if you haven't used it)

13


VIP:

do not edit these