Con la visibilidad de las variables a lo largo del código
let¶
Análisis del código con alcance léxico:
(let ((x 6) (y 7)) ; Nivel 1: x=6, y=7
(*
(let ((y 8)) ; Nivel 2: y=8 (oculta y=7)
(+
(let ((x 6) (y x)) ; Nivel 3: x=6, y=x → y=6 (del nivel 1)
(+ x
(let ((y 3) (x y)) ; Nivel 4: y=3, x=y → x=6 (del nivel 3)
(+ x (+ 2 y)))))
y)) ; y=8 (del nivel 2)
(let ((x 4)) ; Nivel 2': x=4 (oculta x=6)
(- y x)))) ; y=7 (del nivel 1), x=4
Evaluación paso a paso:
- Nivel 1:
x=6,y=7 - Primer
letinterno (nivel 2):y=8(ocultay=7) letanidado (nivel 3):x=6,y=x→y=6(tomaxdel nivel 1)letmás interno (nivel 4):y=3,x=y→x=6(tomaydel nivel 3)(+ x (+ 2 y))→(+ 6 (+ 2 3))→11- Nivel 3 continúa:
(+ x [resultado 11])→(+ 6 11)→17 - Nivel 2 continúa:
(+ [resultado 17] y)→(+ 17 8)→25 - Segundo
let(nivel 2'):x=4(ocultax=6del nivel 1) (- y x)→(- 7 4)→3(tomay=7del nivel 1,x=4local)- Operación final:
(* [resultado 25] [resultado 3])→75
Resultado final: 75
Reglas de alcance léxico:
- Cada let crea un nuevo ámbito anidado
- Las referencias a variables resuelven al binding más cercano en el ámbito léxico
- Los bindings internos ocultan los externos con el mismo nombre
- Las referencias siempre usan el valor del ámbito donde fueron definidas, no donde son usadas
let*¶
Es similar la let pero las ligaduras se conocen entre sí por el orden creación
Análisis del código con let* (enlace secuencial):
(let* ((x 6) (y 7)) ; x=6, y=7
(*
(let* ((y 8)) ; y=8
(+
(let* ((x 6) (y x)) ; x=6, y=x → y=6 (x del binding anterior)
(+ x
(let* ((y 3) (x y)) ; y=3, x=y → x=3 (y del binding anterior)
(+ x (+ 2 y)))))
y)) ; y=8
(let* ((x 4)) ; x=4
(- y x)))) ; y=7 (ámbito externo), x=4
Evaluación paso a paso:
- Primer
let*:x=6,y=7 let*interno 1:y=8(ocultay=7)let*interno 2:x=6,y=x→y=6(usax=6del binding anterior en el mismolet*)let*interno 3:y=3,x=y→x=3(usay=3del binding anterior)(+ x (+ 2 y))→(+ 3 (+ 2 3))→8- Continúa
let*interno 2:(+ x [resultado 8])→(+ 6 8)→14 - Continúa
let*interno 1:(+ [resultado 14] y)→(+ 14 8)→22 let*final:x=4(- y x)→(- 7 4)→3(tomay=7del ámbito más externo)- Operación final:
(* [resultado 22] [resultado 3])→66
Resultado final: 66
Diferencia clave con let normal:
- let* permite referenciar bindings anteriores en el mismo let*
- En (let* ((x 6) (y x)), y puede usar el valor de x recién bindeado
- En let normal esto produciría error porque todos los bindings son paralelos
Ámbito léxico se mantiene: Las referencias a variables fuera de los let* siguen las mismas reglas de ámbito léxico que let normal.
letrec¶
Aclaración correcta: Efectivamente, letrec funciona como let* para valores no recursivos, pero permite referencias mutuas para funciones.
Código corregido y análisis:
(letrec ((x 6) (y 7) (f (lambda (x y) (if (> x 0) (f (- x 1) y) y))))
(*
(letrec ((y 8)) ; y=8
(+
(letrec ((x 6) (y x)) ; x=6, y=x → y=6 (usa x del mismo letrec)
(+ x
(letrec ((y 3) (x y)) ; y=3, x=y → x=3 (usa y del mismo letrec)
(+ x (+ 2 y))))) ; (+ 3 (+ 2 3)) = 8
y)) ; y=8 → (+ 14 8) = 22
(letrec ((x 4)) ; x=4
(- y x)))) ; y=7 (ámbito externo), x=4 → (- 7 4)=3
Evaluación paso a paso:
- Ámbito principal:
x=6,y=7,fdefinida - Primer
letrecinterno:y=8 - Segundo
letrecinterno:x=6,y=x→y=6(referencia válida dentro del mismoletrec) (+ x ...)→(+ 6 ...)- Tercer
letrecinterno:y=3,x=y→x=3(referencia válida) (+ x (+ 2 y))→(+ 3 (+ 2 3))→8- Resultado nivel 3:
(+ 6 8)→14 - Resultado nivel 2:
(+ 14 8)→22 - Cuarto
letrec:x=4 (- y x)→(- 7 4)→3(tomay=7del ámbito principal)- Resultado final:
(* 22 3)→66
Comportamiento de letrec:
- Para valores simples: funciona como let* - cada binding puede referenciar bindings anteriores en el mismo letrec
- Para funciones: permiten autorreferencia y referencias mutuas entre bindings del mismo letrec
- El ámbito se crea de una vez, permitiendo referencias circulares entre todos los bindings
Resultado final: 66 (igual que con let* en este caso específico)