Table of Contents

Tarea 3 (Entrega: 21 de julio de 2024)

Esta tarea se distribuye con un archivo zip ( base) que contiene 3 archivos: main.rkt, tests.rkt y env.rkt. Los archivos están incompletos, y en ellos tiene que implementar lo que se solicita en las preguntas siguientes.

Debe entregar via U-cursos un archivo .zip que contenga los archivos main.rkt y tests.rkt.

Consulte las normas de entrega de tareas en http://pleiad.cl/teaching/cc4101

Recuerde que el testing y calidad de código se evalúan de acuerdo a la rúbrica.

Resumen

El objetivo de la tarea es extender un lenguaje base para soportar clases y objetos. El lenguaje base tiene números, booleanos y operaciones sobre ellos. Además contiene expresiones begin y with para secuenciar expresiones y definir múltiples identificadores respectivamente; pero no tiene soporte para funciones. Tome un tiempo de experimentar con el lenguaje entregado antes de comenzar a implementar las extensiones requeridas.

La tarea está dividida en 2 secciones, las cuales se describen a continuación:

- Clases y objetos: En esta sección se pide extender el lenguaje base con clases y objetos. En particular las clases deben ser entidades de primera clase, es decir, son valores del lenguaje.

- Codificando funciones anónimas de primera clase con Objetos: El objetivo de esta sección es extender el lenguaje para soportar funciones anónimas de primera clase (típicamente conocidas como “lambdas”) y aplicaciones como azúcar sintáctica, usando objetos.

  • La tarea se debe realizar SIN usar macros.
  • El objetivo de la tarea es que implemente un intérprete sintáctico para el lenguaje orientado a objetos que se presenta. Esto quiere decir en particular que las clases y objetos no pueden ser codificados con clases/objetos o lambdas de Racket (*). Tienen que ser representados como estructuras de datos.

En resumen, deben usar los conceptos de OOP vistos en clase y que también se presentan en el apunte OOPLAI.

(*) nada de (lambda (msg . args) …) en su código!

Clases y objetos (5 ptos.)

A continuación se presenta la sintaxis concreta del lenguaje extendido (se omiten las expresiones del lenguaje base):

<expr> ::= ... (expresiones del lenguaje base) ...
        | {class {<sym>*} <method>*}
        | {new <expr> <expr>*}
        | {get <expr> <sym>}
        | {set <sym> <expr>}
        | {-> <expr> <sym> <expr>*}
        | self
 
<method> ::= {def <sym> {<sym>*} <expr>}

Donde:

A lo largo de la implementación, verificaremos que los programas del lenguaje cumplan múltiples requisitos. Por ejemplo, revisaremos que no hayan campos duplicados dentro de una clase, o que los usos de self estén sólo dentro de un método. Todas las verificaciones que puedan hacerse de manera estática estarán centralizadas en una función llamada well-formed. En el archivo base main.rkt se entrega una implementación inicial que deberá extender siguiendo las indicaciones del enunciado.

Veamos algunos programas de ejemplo para ilustrar de manera general las características esperadas del lenguaje.

;; comportamiento esperado
> (run-val '{with {{c {class {x y}
                        {def init {}
                          {begin {set x 1} {set y 2}}}
                        {def init {init-x init-y}
                          {begin {set x init-x} {set y init-y}}}  
                        {def sum {z} {+ {get self x} {+ {get self y} z}}}
                        {def set-x {val} {set x val}}}}
                   {o {new c 3 4}}}
              {begin
                {-> o set-x {+ 1 3}}
                {+ {-> o sum 3} {get o y}}}})
15
;; las clases son valores
> (run-val '{with {{A {class {}
                        {def apply {c} {-> {new c} m}}}}
                   {o {new A}}}
              {-> o apply {class {x}
                            {def init {} {set x 2}}
                            {def m {} {get self x}}}}})
2
;;la definición de la clase tiene scope léxico
> (run-val '{begin {with {{A {class {x}
                               {def init {} {set x 2}}
                               {def init {init-x} {set x init-x}}
                               {def m {} {get self x}}}}}
                     10}
                   {new A}})
"free identifier: A"
;; los identificadores dentro de una clase tienen scope léxico 
;; (note el uso de la “x” en la definición del método “m”
> (run-val '{with {{x 10}
                   {A {class {}
                        {def m {y} {+ x y}}}}
                   {o {new A}}}
              {-> o m 1}})
11            

Manos a la Obra!

Clases (1.5 pts)

Extensiones del AST y Parser

Extensiones de well-formed

Durante la creación de clases, deben cumplirse los siguientes requisitos:

A continuación se muestran algunos ejemplos de los errores que pueden lanzarse al hacer las verificaciones:

;; Crear una clase con campos duplicados es un error en tiempo de creación de la clase
> (run-val '{begin {with {{A {class {x y x}}}}
                     10}
                   {new A}})
"error: duplicate fields"
;; Tener 2 init con la misma aridad es un error en tiempo de creación de la clase
> (run-val '{begin {with {{A {class {x}
                               {def init {init-x} {set x init-x}}
                               {def init {init-x} {set x 12}}}}}
                     10}
                   {new A}})
"error: same arity constructor"
;; Tener 2 métodos con la misma aridad es un error en tiempo de creación de la clase
> (run-val '{begin {with {{A {class {x}
                               {def foo {a} {set x a}}
                               {def foo {b} {set x {+ b 1}}}}}}
                     10}
                   {new A}})
"error: overloading method foo with the same arity"
;; No se puede usar self fuera de un método
> (run-val 'self)
"error: self outside of method"

Observación: Puede añadir un parámetro extra a well-formed para indicar si esta dentro de una clase

Intérprete

Observación: Recuerde que los métodos definidos en una clase deben utilizar el ambiente al momento de la creación de la clase y no el ambiente de cuando son invocados.

Objetos (1.5 pts)

Extensiones del AST y Parser

Extensiones de well-formed

Intérprete

Observación: Cuando se evalúa una expresión new, se debe buscar en la clase un constructor que corresponda al número de argumentos entregados. Si no hay ninguno que tenga la aridad requerida, se debe lanzar el error “constructor not found”. Si se está instanciando una clase que no declara ningún constructor, solo se puede usar el constructor por defecto que no recibe argumentos, es decir, {new c}.

A continuación se muestran algunos ejemplos de instanciación de clases:

;; Una clase sin constructores puede ser creado solo con {new class}, sin argumentos
> (run-val '{with {{x 10}
                   {A {class {x}}}
                   {o {new A x}}}
              1})
"error: constructor not found"
;; Esta clase no tienen ningún constructor con aridad 2
> (run-val '{begin {with {{C {class {x}
                               {def init {init-x} {set x init-x}}}}}
                     10}
                   {new C 1 2}})
"error: constructor not found"

Llamados a Métodos (0.8 pto)

Extensiones del AST y Parser

Extensiones de well-formed

Intérprete

Observaciones:

A continuación se muestran programas en los que se intenta invocar un método inexistente o donde no se encuentra una sobrecarga con la aridad correcta:

;; Invocar un método no definido de una clase
> (run-val '{with {{A {class {}}}
                   {o {new A}}}
              {-> o m}})
"error: method m not found"
;; Esta clase no tiene el método set-x definido para la aridad 2
> (run-val '{with {{A {class {x}
                        {def set-x {val-x} {set x val-x}}}}
                   {o {new A}}}
              {-> o set-x 10 20}})
"error: method set-x not found"

Acceso y Modificación a Campos (1.2 pts)

Extensiones del AST y Parser

Extensiones de well-formed

Durante el instanciado de clases, deben cumplirse los siguientes requisitos:

;; No se puede usar set fuera de un método
> (run-val '{set x 1})
"error: set outside of method"

Intérprete

Los errores dentro de interp para objetos deben manejarse de la siguiente forma:

;; Acceder a un campo no definido de una clase
> (run-val '{with {{A {class {}}}
                   {o {new A}}}
              {get o z}})
"error: field z not found"
;; Acceder a un campo no inicializado de una clase
> (run-val '{with {{A {class {x y} {def init {init-x init-y} {set x init-x}}}}
                   {o {new A 1 2}}}
              {get o y}})
"error: field y not initialized"

Codificando funciones anónimas de primera clase con Objetos (1 pto)

Ahora incorporaremos funciones anónimas de primera clase (típicamente conocidas como “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 idear una manera de usar la implementación de clases y objetos hecha en la parte anterior para codificar las lambdas. Esto significa que no puede modificar el AST y el intérprete para soportar funciones y aplicaciones de funciones. Las modificaciones que debe hacer son en el parser. En otras palabras, las funciones y aplicaciones serán sólo azúcar sintáctica.

Hint: Piense en lo que comúnmente hemos visto como sintaxis de “aplicación de función” como azúcar sintáctico para la invocación de un método en un objeto, este método puede contener el código de la “función” definida. ¿De qué clase sería ese objeto? ¿Cómo se podría llamar ese método?

<expr> ::= ...
         | (fun (<id>*) <expr>)
         | (<expr> <expr>*)

Ejemplos de uso:

> (run-val '{{fun {x} {+ x 1}} 2})
3
> (run-val '{with {{add1 {fun {x} {+ x 1}}}}
                  {+ {add1 2} {add1 4}}})
8