Tarea 3 (Entrega: 8 de julio de 2021)

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.

  • la tarea se debe realizar SIN usar macros
  • los objetos NO deben ser codificados como lambdas: un objectV es una estructura que agrega campos y métodos (y de alguna forma, el ambiente para las variables libres), así como su padre en el caso de la delegación. (*)

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!

Objetos como valores (3,0 ptos.)

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.
Para realizar modificaciones con 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.
  • una definición de campo (field) incluye una expresión de inicialización, importa el orden en el cual están declarados y usados.
  • una definición de método (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:

  • El acceso a un campo inexistente de un objeto debe arrojar el error “field not found”.
  • La invocación de un método inexistente debe lanzar el error “method not found”.
  • Solamente es válido usar 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
Orden de evaluación:
  • Dentro de una expresión (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
  • Dentro de una expresión (send e method-id args) se evalua e en primer lugar y después los argumentos args de izquierda a derecha.

Objetos con delegación (2,0 puntos)

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))

Copias de objetos con delegación (1,0 punto)

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
 

[Bonus] Codificando lambdas con objetos (0,5 ptos.)

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