De algunos de los estudiantes de Kent, he aprendido lo siguiente: letrec
se implementa en términos de let
mediante macro expansión. Se expande el letrec
en un let
que usa set!
dentro. Así que el primer ejemplo podría ampliar a esto:
(let
([sum (void)])
(set! sum (lambda (x) (if (zero? x) 0 (+ x (sum (- x 1))))))
(sum 5))
Su segundo, de manera similar (tenga en cuenta que los anidados let
s son el resultado de la let*
- También, esto puede no ser una expansión del todo correcta, pero es mi mejor conjetura):
(let
([sum (void)]
(set! sum (lambda (x) (if (zero? x) 0 (+ x (sum (- x 1))))))
(let
[f (void)]
(set! f (lambda() (cons n n-sum)))
(let
[n (void)]
(set! n 15)
(let
[n-sum (void)])
(set! n-sum (sum n))
(f))
no estoy 600% seguro de cómo el llamado let
expande, pero Eli sugiere que se llevaría a cabo en términos de letrec
en sí (que tiene sentido y debe ser bastante obvio). Por lo tanto, su nombre let
se mueve desde un nombre let
en un letrec
en un let
sin nombre. Y su reescritura del segundo se ve casi exactamente como la expansión de todos modos.
Si está interpretándolo y buscando un buen rendimiento, me inclinaría hacia letrec
porque es un paso más corto de expansión macro. Además, let
se convierte en una lambda, por lo que está utilizando define
s en su segundo ejemplo en lugar de set!
s (que puede ser más pesado).
Por supuesto, si estás compilando, es probable que todo caiga en el compilador de todas formas por lo que sólo tiene que utilizar lo que usted piensa que se ve mejor (soy parcial a letrec
porque let
bucles me recuerdan a la programación imperativa, pero tu caso es distinto). Dicho esto, debe ser usted, estilísticamente (ya que son más o menos equivalentes).
Dicho esto, te voy a dar un ejemplo de que es posible que valga la pena:
(letrec
([even? (lambda (n) (if (zero? n) #t (odd? (- n 1))))]
[odd? (lambda (n) (if (zero? n) #f (even? (- n 1))))])
(even? 88))
Usando su estilo interno define
producirá:
(let()
(define even? (lambda (n) (if (zero? n) #t (odd? (- n 1)))))
(define odd? (lambda (n) (if (zero? n) #f (even? (- n 1)))))
(even? 88))
Así que aquí el código letrec
es en realidad más corto. Y, sinceramente, si está haciendo algo así como este último, ¿por qué no conformarse con begin
?
(begin
(define even? (lambda (n) (if (zero? n) #t (odd? (- n 1)))))
(define odd? (lambda (n) (if (zero? n) #f (even? (- n 1)))))
(even? 88))
Sospecho que begin
es más de un built-in y, como tal, no va a conseguir-macro expandida (como let
voluntad). Finalmente, a similar issue se ha generado en el desbordamiento de la pila Lisp hace un momento con más o menos el mismo punto.
¿Alguna vez eres rápido Eli :-) Y gracias por tus explicaciones increíblemente claras. –
Una pregunta más Eli. Usted dijo que el nombre Let no es una forma primitiva, sino que generalmente se implementa como un letrec. ¿El letrec suele ser una forma primitiva o es nosotros usualmente implementados como un conjunto con let's! De manera similar, se deja una forma primitiva o se implementa generalmente como una lambda. Gracias. –
@HarrySpier: 'letrec' es generalmente primitivo, aunque a menudo hace algo similar a un' let' y 'set!' S. (Es algo que casi nunca importa, pero puede exponerse usando continuaciones). En cuanto a 'let', creo que hay mucho menos acuerdo, con algunas implementaciones que usan' lambda' como expansión y otras que lo tratan como primitivo. –