Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
teaching:cc4101:tareas:2016-1:tarea1 [2016/03/29 01:31] – created fmosso | teaching:cc4101:tareas:2016-1:tarea1 [2016/04/17 00:03] (current) – [Tarea 1 (Entrega: 20 de Abril 2016)] fmosso | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | tarea | + | ====== Tarea 1 (Entrega: 20 de Abril 2016) ====== |
+ | |||
+ | |||
+ | Esta tarea se distribuye con un archivo zip {{: | ||
+ | |||
+ | * La **carpeta A** contiene 3 archivos: main.rkt, tests.rkt, y machine.rkt. Los archivos main.rkt y tests.rkt son incompletos, | ||
+ | |||
+ | * La **carpeta B** contiene solamente machine.rkt, | ||
+ | |||
+ | * La **carpeta C** viene vacía. Si optan por no hacer la pregunta opcional, tendrán que poner ahí los tres archivos main/ | ||
+ | |||
+ | Deben entregar via U-cursos **un archivo .zip** que contenga las carpetas ante mencionada (obviamente, | ||
+ | |||
+ | Consulte las normas de entrega de tareas en http:// | ||
+ | ¡Ojo que los tests forman parte de su evaluación! | ||
+ | ===== 1. Funciones de primera clase con tipos declarados [1pt] ===== | ||
+ | En esta tarea haremos extensión a un lenguaje con funciones de primera clase con tipos. La gramática del lenguaje es la mostrada a continuación: | ||
+ | < | ||
+ | | < | ||
+ | | < | ||
+ | | {+ < | ||
+ | | {- < | ||
+ | | {= < | ||
+ | | {< < | ||
+ | | {and < | ||
+ | | {or < | ||
+ | | {not < | ||
+ | | {if < | ||
+ | | {with {<id> : < | ||
+ | | {fun {<id> : < | ||
+ | | {< | ||
+ | |||
+ | < | ||
+ | | Bool | ||
+ | | {< | ||
+ | </ | ||
+ | Fíjense que '' | ||
+ | Por ejemplo, los programas siguientes son válidos: <code scheme> | ||
+ | {with {x : Num 5} {+ x 3}} | ||
+ | |||
+ | {fun {x : Num} : Num | ||
+ | {+ x 3}} | ||
+ | |||
+ | {fun {x : Num} | ||
+ | {+ x 3}} | ||
+ | </ | ||
+ | |||
+ | - (0.4) Defina la función '' | ||
+ | > (parse-type '{{Num -> Num} -> Bool}) | ||
+ | (TFun (TFun (TNum) (TNum)) (TBool)) | ||
+ | |||
+ | > (parse-type '{Num -> }) | ||
+ | "Parse error" | ||
+ | </ | ||
+ | - (0.6) Defina la función '' | ||
+ | > (parse '{fun {x : Num} : Num {+ x 1}}) | ||
+ | (fun 'x (TNum) (add (id 'x) (num 1)) (TNum)) | ||
+ | > (parse '{with {y : Num 2} | ||
+ | {+ x y}}) | ||
+ | (app (fun 'y (TNum) (add (id 'x) (id 'y)) #f) (num 2)) | ||
+ | </ | ||
+ | |||
+ | Note que se usa '' | ||
+ | |||
+ | |||
+ | |||
+ | ===== 2. Compilación [2pt]===== | ||
+ | Ahora vamos a compilar nuestro lenguaje a una máquina abstracta basada en stack. La máquina es similar a la conocida SECD(([[https:// | ||
+ | La función '' | ||
+ | > (machine (list (INT-CONST 6) (INT-CONST 1) (ADD))) | ||
+ | 7 | ||
+ | </ | ||
+ | Tome un tiempo para experimentar con la función '' | ||
+ | ==== Índices De Bruijn ==== | ||
+ | Una forma de eliminar nombres de variables o identificadores de una expresión, es usando índices De Bruijn. Anteriormente vimos que la máquina SECD trabaja solo con índices, por lo que en esta sección vamos a hacer el paso de identificadores a dichos números, usando indices De Bruijn((La explicación detallada de como funcionan se puede encontrar en la sección 3.5 del [[http:// | ||
+ | * (1.0) Implemente la función '' | ||
+ | hint: le podría ser útil recordar que el interprete visto en clases usa un ambiente para asociar identificadores a valores. | ||
+ | |||
+ | <code scheme> | ||
+ | > (deBruijn | ||
+ | | ||
+ | {with {y : Num 2} | ||
+ | {+ x y}}}})) | ||
+ | (add | ||
+ | (num 1) | ||
+ | | ||
+ | (fun-db | ||
+ | (app (fun-db (add (acc 1) (acc 0))) (num 2))) | ||
+ | (num 1))) | ||
+ | |||
+ | > (deBruijn (add (num 1) (id ' | ||
+ | "Free identifier: x" | ||
+ | </ | ||
+ | ==== De AST a código de máquina ==== | ||
+ | Vamos abordar la compilación del AST de una expresión al set de instrucciones de la SECD. | ||
+ | A continuación se presenta el esquema de compilación que permite hacer el paso del lenguaje original a listas de instrucciones. Note que solo es el paso a notación polaca inversa: | ||
+ | |||
+ | C(n) = INT-CONST n | ||
+ | C(b) = BOOL-CONST b | ||
+ | C(n) = ACCESS n | ||
+ | C({+ a1 a2}) = C(a2); | ||
+ | C({- a1 a2}) = C(a2); | ||
+ | C({< a1 a2}) = C(a2); | ||
+ | C({= a1 a2}) = C(a2); | ||
+ | C({and a1 a2}) = C(a2); | ||
+ | C({or a1 a2}) = C(a2); | ||
+ | C({not a1}) = C(a1);NOT | ||
+ | C({f a}) = C(a); C(f); APPLY | ||
+ | C({if c tb fb}) = C(c); IF(C(tb), | ||
+ | |||
+ | </ | ||
+ | La compilación de una función puede ser inferida del ejemplo a continuación: | ||
+ | > (compile (deBruijn (parse '{{fun {x : Num} : Bool | ||
+ | {< x 10}} {+ 2 3}}))) | ||
+ | (list | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | |||
+ | |||
+ | * (1.0) Implemente la función '' | ||
+ | |||
+ | |||
+ | ===== 3. Verificación de Tipos [1.5pt] ===== | ||
+ | Se implementará el chequeo de tipos de una expresión en presencia de funciones de primera clase. | ||
+ | * (1.0) Implemente la función '' | ||
+ | * Para el if ambas ramas deben ser del mismo tipo, es decir al tipear '' | ||
+ | * Para una función con el tipo de retorno anotado, se válida que la expresión del cuerpo tenga el mismo tipo que el tipo de retorno declarado. | ||
+ | * Para una función sin el tipo de retorno anotado, el tipo resultante considera el tipo calculado del cuerpo de la función. | ||
+ | * Para la aplicación de función se valida que la expresión en posición de función es efectivamente una función y que el tipo de la expresión de argumento de la aplicación coincide con el tipo esperado del argumento de la función. | ||
+ | |||
+ | Los mensaje de error de tipo tienen que seguir el siguiente patrón: | ||
+ | <code scheme> | ||
+ | "Type error in expression <E> position <n>: expected <T1> found < | ||
+ | </ | ||
+ | |||
+ | donde E es la expresión donde ocurrió el error, n es la sub-expresión donde se manifestó, T1 es el tipo esperado y T2 el tipo encontrado. | ||
+ | |||
+ | Para el caso cuando hay algún identificador libre, el patrón es: | ||
+ | <code scheme> | ||
+ | "Type error in expression id position 1: No type for identifier < | ||
+ | </ | ||
+ | |||
+ | donde <x> es el identificador libre | ||
+ | |||
+ | por ejemplo: | ||
+ | |||
+ | <code scheme> | ||
+ | > (typeof (parse '{fun {f : Num} : Bool #t})) | ||
+ | (TFun (TNum) (TBool)) | ||
+ | > (typeof (parse '{fun {f : Num} #t})) | ||
+ | (TFun (TNum) (TBool)) | ||
+ | > (typeof (parse '{fun {f : Num} : Bool 10})) | ||
+ | "Type error in expression fun position 3: expected Bool found Num" | ||
+ | > (typeof (parse '{{fun {f : Num} : Bool #t} #t})) | ||
+ | "Type error in expression app position 2: expected Num found Bool" | ||
+ | > (typeof (parse 'y)) | ||
+ | "Type error in expression id position 1: No type for identifier: y" | ||
+ | </ | ||
+ | |||
+ | * (0.3) Implemente la función '' | ||
+ | |||
+ | <code scheme> | ||
+ | > | ||
+ | 'Num | ||
+ | |||
+ | > | ||
+ | '{Num -> Bool} | ||
+ | |||
+ | > | ||
+ | "Type error in expression + position 2: expected Num found Bool" | ||
+ | |||
+ | > | ||
+ | "Type error in expression if position 3: expected Num found Bool" | ||
+ | |||
+ | </ | ||
+ | |||
+ | * (0.2) Implemente la función '' | ||
+ | |||
+ | |||
+ | |||
+ | ===== 4. Sistema de tipos con subtipos [1.5pt] ===== | ||
+ | |||
+ | //Recuerde: esta pregunta se entrega en la carpeta B// | ||
+ | |||
+ | Vamos a aumentar la expresividad de nuestro sistema de tipos introduciendo una forma básica de // | ||
+ | La relación de subtipos se escribe '' | ||
+ | La intuición es que es válido usar una expresión de tipo A donde se espera una expresión de tipo B. | ||
+ | |||
+ | Introducimos el tipo Any como supertipo universal. Es decir, para todo tipo T, tenemos '' | ||
+ | |||
+ | Tener '' | ||
+ | Como otro ejemplo, la función '' | ||
+ | |||
+ | Algo interesante surge al considerar tipos de funciones. La regla es que un tipo de función '' | ||
+ | Notar que la relación de sub-tipado de los tipos de los cuerpos esta en el mismo sentido (// | ||
+ | |||
+ | |||
+ | * (1.0) Extienda la definición de los tipos para soportar '' | ||
+ | |||
+ | |||
+ | Si bien el sub-tipado permite aceptar más programas, tener un valor del tipo '' | ||
+ | |||
+ | Por ejemplo: | ||
+ | <code scheme> | ||
+ | {+ {{fun {x : Any} x} 1} | ||
+ | 2} | ||
+ | </ | ||
+ | es rechazado por el sistema de tipo, ya que '' | ||
+ | |||
+ | Para solucionar este problema, los lenguajes de programación con subtipos incluyen mecanismos que permiten que el programador le fuerce la mano al sistema de tipos: decirle que una expresión de un tipo '' | ||
+ | |||
+ | En esta parte, tienen que implementar coerciones, sin verificación dinámica (agregar verificación dinámica se deja como pregunta opcional al final de esta tarea). | ||
+ | |||
+ | * (0.5) agregue al lenguaje la expresión '' | ||
+ | |||
+ | <code scheme> | ||
+ | > | ||
+ | 'Num | ||
+ | |||
+ | > | ||
+ | 'Num | ||
+ | |||
+ | > | ||
+ | 'Num | ||
+ | </ | ||
+ | |||
+ | Note que compilar una coerción significa " | ||
+ | |||
+ | ===== Bonus: Safe Coerce [+1pt] ===== | ||
+ | |||
+ | //Recuerde: esta pregunta es opcional, si la hace, se entrega en la carpeta C// | ||
+ | |||
+ | Para evitar que una coerción invalida lleve la máquina de ejecución a un estado errado, se le pide que modifiquen su tarea para que las coerciones sean safe: tal como un cast en Java, se verifica dinámicamente que el tipo real del valor es compatible con el tipo de la coerción. |