Esta tarea se distribuye con un archivo zip (base-tarea3) que contiene 2 archivos: main.rkt y tests.rkt. Los archivos main.rkt y tests.rkt están incompletos, y en ellos tienen que implementar lo que se solicita en las preguntas siguientes.
Deben entregar via U-cursos un archivo .zip que contenga los archivos main.rkt y tests.rkt.
¡Los tests, los contratos y las descripciones forman parte de su evaluación!
En esta tarea extenderemos un lenguaje base para soportar objetos y delegación. El lenguaje base tiene números, booleanos y operaciones sobre ellos. Además contiene expressiones if
, seqn
, local
y define
, pero no tiene soporte para funciones. Se emplea call-by-value como estrategia de evaluación. Tome un tiempo de experimentar con el lenguaje entregado antes de comenzar a implementar las extensiones pedidas.
En resumen, del OOPLAI, tienen que reusar *los conceptos*, no el código. Además, pueden usar OBJECT-DEL del OOPLAI como semántica de referencia, para consultar como se debe comportar su lenguaje.
(*) nada de (lambda (msg . args) …)
en su código!
En esta sección se extenderá el lenguaje MiniScheme+ con objetos.
A continuación se presenta la sintaxis del lenguaje extendido (se omiten las del lenguaje base):
<expr> ::= ... (expresiones del lenguage entregado) ... | (object <def>*) | (get <id>) | (set <id> <expr>) | (send <expr> <id> <expr>*) | this <def> ::= (field <id> <expr>) | (method <id> (<id>*) <expr>)
Donde:
object
permite crear un nuevo objeto (anónimo), con definiciones de campos y métodos.get
y set
permiten acceder a un campo del objeto corriente o modificarlo.set
puede usar mutación. Es decir, no se requiere el uso de un store
.
send
permite invocar un método de un objeto dado, con 0 o más argumentos.this
permite acceder al objeto actual. field
) incluye una expresión de inicialización, importa el orden en el cual están declarados y usados.method
) especifica 0 o más parámetros, y el cuerpo del método.Los objetos son tratados como valores. Usted debe decidir los atributos que tendrán sus respectivos nodos en el AST: piensan bien en que se necesita para evaluar un objeto, en particular con respeto al ambiente. Modifique el parser y el intérprete para soportar el lenguaje extendido. Además, los errores deben manejarse de la siguiente forma:
this
, set
y get
dentro de un objeto. Al exterior debe lanzar el erro “… used outside of an object”.Veamos algunos ejemplos de objetos en acción:
;; comportamiento esperado > (run-val '(local [(define o (object (field x 1) (field y 2) (method sum (z) (+ (get x) (+ (get y) z))) (method set-x (val) (set x val)) (method get-y () (get y))))] (seqn (send o set-x (+ 1 3)) (+ (send o sum 3) (send o get-y))))) 11
;; los objetos son valores > (run-val '(local [(define a (object (method auto-apply (o) (send o apply o)) (method foo () 5))) (define o (send a auto-apply (object (method apply (other) (send other apply2 this)) (method apply2 (other) this) (method foo () 42))))] (send o foo))) 42
(object [: e] members)
se evalua e
en primer lugar y después los campos en el orden en que aparecen. Donde e es un objeto donde se delegan los mensajes no entendidos por el objeto creado(send e method-id args)
se evalua e
en primer lugar y después los argumentos args
de izquierda a derecha. En esta sección deberá implementar un sistema de delegación para objetos, similar al que poseen lenguajes como Self o Javascript. Antes de hacer este ejercicio, lea cuidadamente la sección 4 de OOPLAI.
La sintaxis se debe extender de la siguiente manera:
<expr> ::= ... (expresiones previas) ... | (object [: <expr>] <def>*)
Donde los paréntesis cuadrados implican opcionalidad. En caso de estar presente la expresión opcional en primera posición, esta debe evaluar a un objeto, al cuál se deben delegar los mensajes que el objeto definido no comprende, siguiendo la semántica de delegación.
Note que debe mantener el soporte para expresiones de la forma (object <def>*)
, para crear objetos cuyos mensajes incomprendidos no son delegados.
> (run-val '(local [(define smart-computer (object (method secret? (something) 42))) (define everything (object)) (define oracle (object : smart-computer))] (send oracle secret? everything))) 42
Su implementación debe asegurar la semántica de delegación.
> (run-val '(local [(define seller (object (method multiplier () 1) (method price (item-number) (* item-number (send this multiplier))))) (define broker (object : seller (method multiplier () 2)))] (send broker price 3))) 6 > (run-val '(local ([define x (object (field z 3) (method get () (get z)))] [define y (object : x)]) (send y get))) 3 > (run-val '(local ([define x (object (field z 3) (method get () (get z)))] [define y (object : x (method get () (get z)))]) (send y get))) "field not found"
Considere además el siguiente programa
(run-val '(local ([define x (object (field f 3) (method f-getter () (get f)) (method get () (send z msg)))] [define y (object : x (field f 5) (method msg () (send this f-getter)) (method f-getter () (get f)))] [define z (object : y (field f 8) (method f-getter () (get f)))]) (send y get)))
los define
en un mismo bloque local
son mutuamente visibles, tal como en Racket. Así que el z
del método get de x
, está en el scope de x
, por lo que el programa retorna 8.
(internamente, local
introduce un frame de definiciones (como un slot del env), y define
agrega la definición al frame actual). Atención de que z está dentro de un método, es decir, no es necesario inmediatamente como en este programa que da free identifier.
(run-val '(local ([define x y] [define y z] [define z 2]) x))
Agregue dos operaciones shallow-copy y deep-copy que permiten replicar un objeto.
<expr> ::= ... (expresiones previas) ... | (shallow-copy <expr>) | (deep-copy <expr>)
La expresión shallow-copy crea un objeto nuevo duplicando los campos del objeto dado, y que tiene los mismos métodos (no intentaremos duplicar los ambientes de los métodos). La expresión deep-copy funciona como shallow-copy
pero además copia la cadena de delegación de manera recursiva.
;; a simple monotone counter > (define counter '(object (field count 0) (method incr () (set count (+ 1 (get count)))) (method get () (get count)))) ;; sequence of operations over 2 counters > (define (incrs-multiply x y) `(seqn (send ,y incr) (seqn (send ,x incr) (seqn (send ,x incr) (* (send ,x get) (send ,y get)) )))) ;; no delegation, shallow-copy -> no sharing > (run-val `(local ([define c ,counter]) (seqn (send c incr) (local ([define c2 (shallow-copy c)]) ,(incrs-multiply 'c 'c2))))) 6 ;; delegation, shallow-copy -> sharing > (run-val `(local ([define c (object : ,counter)]) (seqn (send c incr) (local ([define c2 (shallow-copy c)]) ,(incrs-multiply 'c 'c2))))) 16 ;; delegation, deep-copy -> no sharing > (run-val `(local ([define c (object : ,counter)]) (seqn (send c incr) (local ([define c2 (deep-copy c)]) ,(incrs-multiply 'c 'c2))))) 6
Ahora incorporamos lambdas a nuestro lenguaje. A diferencia de lo visto durante el curso, en esta ocasión no daremos una interpretación directa de las funciones, usted debe ingeniar una manera de codificar una lambda como un objeto. Esto significa que no puede modificar el interprete para soportar funciones, ni aplicaciones de funciones. Las modificaciones que debe hacer son en el parser.
<expr> ::= ... | (fun (<id>*) <expr>) | (<expr> <expr>*)
Ejemplo de uso:
> (run-val '(local [(define f (fun (x) (+ x x)))] (f 5))) 10