Entrega 4
Deadline: 10 de Noviembre 2024
⚠ Importante
Recuerde las reglas del código de conducta.
Objetivos básicos de la entrega:
Implementación de funciones de primera clase
Objetivo extra:
Recursión (0.5pto)
La evaluación se hace con specs grading (vea los detalles aquí).
spec básica 4.5 pts
spec extra 0.5 pts
testing 0.5 pts
calidad del código 0.5 pts
1 Para empezar
Para partir, pueden iniciar un nuevo branch en git con:
$ git checkout -b entrega4
⚠ Comentario
Recuerde que es opcional que hagan el merge con el repositorio de referencia
cdi
, porque resolver/debuggear los conflictor podría ser muy laborioso. Si deciden no hacer el merge, miren el código del nuevo parser y AST como referencia para los cambios que deben hacer. Recuerden seguir la misma sintaxis concreta que usa el código de referencia y que se detalla en este enunciado. Esto es importante, pues su código será revisado con tests escritos usando dicha sintaxis.
Si deciden hacer el merge, pueden hacerlo con:
$ git fetch cdi
$ git merge cdi/entrega4
1.1 Desarrollo y entrega del código
Recuerden hacer un git commit
cada vez que avanzan en su desarrollo, y subir el código a su repositorio Github para facilitar el acceso al código a los auxiliares en
caso de que necesiten ayuda.
Para entregar su solución, hagan un release en Github con nombre entrega4 y entreguen el hash del commit correspondiente en el comentario de la tarea de U-cursos.
Recuerden documentar lo que intentaron y lograron hacer, y cualquier otro detalle relevante a su implementación (e.g. cuál spec avanzada eligieron, decisiones de diseño que tomaron, etc). Para esto tienen dos alternativas:
Agregar la documentación en el texto descriptivo del release de Github
Dejar la documentación en un archivo Entrega4.md en su repositorio
1.2 Testing
Recuerden que la documentación para escribir los tests se encuentra en BBCTester/README.md.
2 Lambdas y Clausuras
Extiendan el lenguage fuente con funciones anónimas:
‹expr› ...( lambda ( IDENTIFIER ... ) ‹expr› )
Para ello, sigan las notas del curso e implementen clausuras para representar funciones anónimas como valores.
Por ejemplo:
(tup (lambda () true) (lambda (x y) (+ x y)))
Retorna:
(tup <clos:0> <clos:2>)
Para aplicar clausuras, vamos a usar una sintaxis específica, usando el keyword @
:
Por ejemplo:
(@ (lambda (x y) (+ x y)) 1 2)
Noten que las clausuras nombradas con let
y funciones definidas con def
NO comparten namespace (las funciones definidas con
def
siguen siendo "globales").
Por ejemplo:
(
(def (f x) (+ x -1))
(let (f (lambda (x) (if x 7 13)))
(tup (@ f true) (f 3)))
)
Retorna:
(tup 7 2)
porque la primera aplicación (@ f true)
busca una variable local f
y la segunda
aplicación (f 3)
busca una función global f
.
⚠ Comentario
Tomen en cuenta que esta es la sintaxis/semántica implementada en el oráculo, y les solicitamos seguirla, por simplicidad.
Este diseño fue elegido porque permite separar sin ambiguedad el uso de lambdas del uso de funciones globales, pues tienen costos de ejecución distintos.
Deben agregar también una verificación dinámica de tipos para la aplicación de lambdas (@)
, es decir, reportar error cuando se intenta aplicar algo que no es una clausura. Además, deben reportar un error de aridad cuando se aplique una lambda a una cantidad incorrecta de argumentos.
Recuerden que los mensajes de error que producen deben tener el mismo formato que especifica el enunciado. Por ejemplo, los siguientes programas
(@ 1 true)
(@ false 1 2)
(@ (tup -2 true) 6)
(@ (lambda (x y) (+ y x)) 4)
(@ (lambda () (tup 1 2)) false 6 (tup))
producen respectivamente los siguientes errores:
Type error: Expected closure but got 1
Type error: Expected closure but got false
Type error: Expected closure but got (tup -2 true)
Arity mismatch: closure expected 2 arguments but got 1
Arity mismatch: closure expected 0 arguments but got 3
Recuerden que los errores de tipos y de aridad son de tiempo de ejecución (runtime).
Siguiendo las notas del curso, extiendan su compilador para soportar funciones como valores de primera clase: Se puede pasar una función a otras funciones y retornar funciones.
Las funciones definidas con def
deberían ahora poder recibir funciones como argumento, pero para poder retornarlas el usuario deberá envolverlas en una expresión lambda
.
Por ejemplo:
(
(def (add3 x) (+ 3 x))
(def (map-pair f p) (tup (@ f (get p 0)) (@ f (get p 1))))
(map-pair (lambda (x) (add3 x)) (tup 39 2))
)
Retorna:
(tup 42 5)
Es decir, deben integrarse totalmente con las funciones anónimas producidas por lambda
.
Por ejemplo:
(
(def (fib n) (if (<= n 1) 1 (+ (fib (+ n -1)) (fib (+ n -2)))))
(def (pair-fun f g p) (tup (@ f (get p 0)) (@ g (get p 1))))
(pair-fun (lambda (x) (+ 3 x))
(lambda (y) (pair-fun (lambda (n) (fib n)) (lambda (z) (+ z y)) (tup y y)))
(tup 39 6))
)
Retorna:
(tup 42 (tup 13 12))
3 Objetivo Extra: Recursión (0.5pto)
Para funciones de primer orden la recursión es gratis: el código de cada función tiene una etiqueta y es suficiente hacer un call
a esa etiqueta.
¡No es tan simple para lambdas! Añada una expresión letrec
para definir funciones anónimas recursivas:
‹expr› ...( letrec ( ( IDENTIFIER ( lambda ( IDENTIFIER ... ) ‹expr› ) ) ... ) ‹expr› )
Por ejemplo:
(letrec
((even (lambda (n) (if (<= n 0) true (@ odd (+ n -1)))))
(odd (lambda (n) (if (<= n 1) true (@ even (+ n -1))))))
(@ odd 13))
Retorna:
true