Implementación de letrec¶
Para implementar letrec en el intérprete, hay varios pasos:
1. Modificar la gramática¶
(expresion ("letrec" (arbno identificador "(" (separated-list identificador ",") ")" "=" expresion) "in" expresion) letrec-exp)
Esta construcción tendrá:
- Lista de símbolos que son los nombres de los procedimientos.
- Lista de listas de símbolos que son los argumentos de los procedimientos.
- Lista de expresiones que son los cuerpos de los procedimientos.
- Una expresión que es el cuerpo del
letrec.
Ejemplo:
letrec
f(x,y) = if >(x,0) then (f sub1(x) y) else 10
g(n,m) = if >(n,0) then (g -(n,2) +(1,m)) else *(m,2)
h(a,b) = if >(a,0) then (h -(a,3) *(2,b)) else b
in
(f 10 20)
- Procedimientos:
'(f g h) - Argumentos:
'((x y) (n m) (a b)) - Cuerpos:
'(if >(x,0).... if >(n,0) ... if >(a,0) ...) - Cuerpo del letrec:
(f 10 20)
2. Modificación del ambiente¶
Se define un nuevo tipo de ambiente para soportar recursión mutua:
(ambiente-extendido-recursivo
(nombre-procedimientos (list-of symbol?))
(argumentos-proc (list-of (list-of symbol?)))
(cuerpos-proc (list-of expresion?))
(old-env ambiente?))
Este ambiente no contiene clausuras preconstruidas, porque las clausuras se generan en tiempo de ejecución cuando se referencian los procedimientos (evaluación perezosa).
La modificación del ambiente implica extender el observador apply-env para manejar este nuevo caso:
(ambiente-extendido-recursivo (procnames lidss cuerpos old-env)
(letrec
(
;; Función auxiliar para buscar una variable en el marco recursivo
(buscar-variable (lambda (procnames lidss cuerpos old-env)
(cond
;; Caso 1: Variable no encontrada en este marco → buscar en el ambiente anterior
[(null? procnames) (apply-env old-env var)]
;; Caso 2: Variable encontrada → generar clausura con el ambiente actual (¡no old-env!)
[(equal? (car procnames) var)
(closure
(car lidss) ; Parámetros formales del procedimiento
(car cuerpos) ; Cuerpo del procedimiento
env) ; Ambiente actual (incluye referencias a sí mismo)
]
;; Caso 3: No coincide con este procedimiento → seguir buscando en el marco
[else
(buscar-variable (cdr procnames) (cdr lidss) (cdr cuerpos) old-env)]
)
))
)
;; Iniciar búsqueda en el marco recursivo
(buscar-variable procnames lidss cuerpos old-env)
)
)
Nota clave: Cuando se encuentra un procedimiento dentro del ambiente extendido recursivo, se genera una clausura que contiene el mismo ambiente (env), no el ambiente anterior (old-env). Esto permite que los procedimientos se referencien a sí mismos y entre sí, implementando así la recursión mutua.
3. Modificación del evaluador¶
Cuando se encuentra una expresión letrec, se evalúa su cuerpo en un ambiente extendido recursivo:
(letrec-exp (procnames idss cuerpos cuerpo-letrec)
(evaluar-expresion
cuerpo-letrec ; Cuerpo del letrec
(ambiente-extendido-recursivo ; Ambiente extendido con procedimientos recursivos
procnames ; Nombres de procedimientos
idss ; Listas de parámetros
cuerpos ; Cuerpos de procedimientos
amb) ; Ambiente anterior
)
)
Conceptos teóricos importantes¶
1. Recursión mutua¶
letrec permite definir múltiples procedimientos que pueden referenciarse entre sí, no solo a sí mismos. Esto es esencial para implementar algoritmos que requieren funciones auxiliares mutuamente recursivas.
2. Evaluación perezosa (call-by-name)¶
La implementación presentada genera clausuras solo cuando se referencian los procedimientos, no durante la extensión del ambiente. Esto es una forma de evaluación perezosa que: - Diferencia el costo de construcción de clausuras hasta que son necesarias - Permite referencias circulares sin causar bucles infinitos durante la construcción
3. Consistencia de ambientes¶
La restricción de que letrec solo puede ligar procedimientos garantiza que:
- No se pueden crear referencias circulares con valores no procedurales
- El ambiente mantiene consistencia semántica
4. Diferencia con let y let*¶
let: Ligaduras independientes, sin referencias entre ellaslet*: Ligaduras secuenciales, cada una puede referenciar las anterioresletrec: Ligaduras mutuamente recursivas, todas pueden referenciarse entre sí
Tabla de resumen¶
| Concepto | Descripción | Implementación clave |
|---|---|---|
| Gramática extendida | Nueva regla para letrec con múltiples definiciones procedurales |
(expresion ("letrec" (arbno identificador "(" (separated-list identificador ",") ")" "=" expresion) "in" expresion) letrec-exp) |
| Ambiente recursivo | Estructura especial para soportar referencias mutuas | (ambiente-extendido-recursivo procnames idss cuerpos old-env) |
| Búsqueda de variables | Algoritmo que genera clausuras al encontrar procedimientos | Función buscar-variable en apply-env |
| Generación de clausuras | Creación diferida de clausuras con ambiente actual | (closure (car lidss) (car cuerpos) env) |
| Evaluación del cuerpo | Cuerpo del letrec evaluado en ambiente extendido recursivo |
(evaluar-expresion cuerpo-letrec (ambiente-extendido-recursivo ...)) |
| Recursión mutua | Capacidad de procedimientos para referenciarse entre sí | Ambiente compartido entre todas las clausuras generadas |
| Evaluación perezosa | Clausuras generadas solo al ser referenciadas | Implementación en buscar-variable vs. construcción anticipada |
Comentarios adicionales¶
-
Ventaja de implementación: Al generar clausuras solo cuando se referencian, se evita construir estructuras innecesarias para procedimientos que nunca se usan en el cuerpo del
letrec. -
Limitación semántica: Esta implementación asume que todas las ligaduras en
letrecson procedurales. Algunos lenguajes permitenletreccon valores no procedurales mediante "cajas" (mutable cells). -
Complejidad temporal: La búsqueda de procedimientos es lineal respecto al número de definiciones en el
letrec. Para muchos procedimientos, podría considerarse una estructura de datos más eficiente. -
Extensibilidad: Este patrón puede extenderse para soportar
letrec*(similar alet*pero con recursión) combinando múltiples ambientes extendidos recursivos. -
Verificación estática: En un intérprete real, se debería verificar que el número de argumentos en cada definición coincida con su uso, aunque esto no se muestra en la implementación básica.
-
Relación con Y-combinator:
letrecpuede verse como azúcar sintáctica que encapsula el uso del combinador de punto fijo para implementar recursión, pero de forma más legible y directa.