Parte 2. Memoización (2 ptos.)
En esta sección van a extender el lenguaje CL con un mecanismo de memoización. La memoización es una técnica de optimización que permite evaluar más rápidamente llamadas a funciones. Una función memoizada tiene asociada una estructura (p.ej. tabla de hash) donde se van guardando los argumentos con que se aplica la función y el resultado de evaluar la función con esos argumento. Luego, si se realiza una nueva aplicación con el mismo argumento, basta con acceder a la memoria para obtener el valor resultante, sin tener que evaluar todo el cuerpo de la función de nuevo.
- (0.3 ptos) Extienda la sintaxis del lenguaje (y por ende, el parser) con la expresión
{mfun {<id>} <CL>}
que permite definir una función anónima memoizada. - (0.2 ptos) Extienda el tipo de dato de los valores con un nuevo constructor
mclosV
para representar una función memoizada en tiempo de ejecución. La única diferencia con clausuras tradicionales es la presencia de una tabla de memoización.
Para implementar la tabla de memoización de una función, usen una tabla de hash (documentación), que permite asociar llaves a valores (las llaves y valores pueden ser de cualquier tipo). A continuación, les dejamos un resumen de la interfaz.
> (define my-table (make-hash)) ;; Crea una tabla de hash nueva, vacía > (hash-set! my-table "key1" 23) ;; Agrega una llave "key1" asociada al valor 23 > (hash-ref my-table "key1") ;; Accede al valor asociado a la llave "key1", si es que existe 23 > (hash-has-key? my-table "key1") ;; Chequea si la llave "key1" está definida en la tabla #t
- (1 pto) Modifique el intérprete para que soporte funciones memoizadas. Recuerde que cada aplicación de una función memoizada debe obtener el valor registrado en la tabla de memoización, y de no estarlo, registrarlo para usos posteriores.
- (0.5 ptos) Usando lo realizado en la Parte 1, implemente tests que evidencien que una función memoizada efectivamente no vuelve a ejecutar su cuerpo cuando la vuelven a aplicar con el mismo argumento.
doble
. Si un programa ejecutara
{doble {printn 3}} {doble {printn 3}}
¿Cuántas veces debería imprimirse el número 3? Piense en la solución antes de ver la respuesta. 1)
Observaciones:
- Cada
mfun
tiene su propia memoria. - Si usan los valores del lenguaje como llaves, se van a encontrar con problemas con el mecanismo de hashing normal dado que los valores son mutables (por las tablas de hash de memoización) 2). Para resolver este problema, en vez de usar
make-hash
para crear sus tablas de hash, tienen que usarmake-hashalw
: este constructor retorna una tabla que usaequal-always?
3) para comparar llaves y por ende se comporta de manera coherente con llaves mutables.
doble
recibe 3, después de que se hace printn
. Para la segunda aplicación recibe 3, también después de imprimir con printn
. Al recibir el 3 por segunda vez, responde lo que tiene en memoria, pero en este punto el 3 ya fue impreso por segunda vez.