Esta es la parte B y C del curso comentado acá. En esta parte usamos Racket, un dialecto de Lisp (para la parte B) y Ruby para la parte C.
Racket es un lenguaje funcional y dinámico (a diferencia de ML que es funcional pero estático). Ruby es orientado a objetos y dinámico. El mensaje más importantes del curso es el contraste entre lenguajes estáticos y funcionales y entre descomposición de problemas funcional o en objetos.
Esta parte del curso es relativamente corta pero difícil. No pude mirar los videos enteros pero repasé las notas para hacer los problemas. Los comentarios generales son iguales a los de la parte A: en líneas generales la calidad del curso es altísima.
La segunda parte no busca revisar las ideas que vimos en ML en otro lenguaje, sino seguir agregando conceptos.
Como es habitual en este tipo de curso, me dediqué sobre todo a los programming assignments (repo).
ASSIGNMENT 4: Streams y lazy evaluation (97%)
En este Assignment practicamos con streams y lazy evaluation. No es muy complicado, aunque la idea de tener colecciones infinitas está bastante ligada a la recursión y puede ser un poco mind bending.
ASSIGNMENT 5: MUPL (93%)
Este es el assignment más difícil del curso. La idea es implementar un lenguaje de programación (Made Up Programming Language). Si bien el lenguaje es relativamente sencillo, incluye first class functions y soporta recursión, todo lo cual hay que implementar usando Racket como metalenguaje.
El problema principal es implementar una función que interprete el código escrito en MUPL. MUPL tienen varios tipos de expresiones (funciones, listas, etc.) y de valores.
Este Assignment me pareció tremendamente difícil. Como al final me fue relativamente bien puedo decir que es bastante gratificante. Siguen pendientes los challenge exercises y los community exercises, que prometen ser interesante, pero recién terminé el básico y estoy muerto.
ASSIGNMENT 6: Tetris (100%)
En este assignment, tenemos que extender una implementación de Tetris que nos dan. Está bueno para aprender a navegar una base de código pre existente. La solución usa una librería de Gráficos (Qt) y un wrapper escrito en Ruby. Tuve bastantes problemas para hacerla andar en Linux, así que terminé haciendo el assignment en una máquina virtual Win XP (!).
Lo que tenemos que hacer es agregar algunas al juego y una funcionalidad nueva que permite hacer trampa. Muy interesante, sobre todo para entender programas escritos por otros (es difícil adquirir experiencia en esto en este tipo de cursos). La solución de Dan es (esperablemente) bastante elegante, aunque hay partes que no entendí del todo.
ASSIGNMENT 7: a language for arithmetic expressions (99%)
En este assignment tenemos que implementar un lenguaje de aritmética en SML y en Ruby. Nos dan la versión de SML bastante avanzada y nosotros tenemos que completarla. La de Ruby implica portar las soluciones desde SML.
El lenguaje tiene líneas y puntos, y permite moverlos e intersectarlos. También tiene lets y variables. La principal lección del assignment es ilustrar como se descompone el problema en el caso funcional en operaciones sobre datatypes.
Así, Point, Line, Shift, Var, etc. son datatypes, y el programa consiste en definiciones de funciones que permiten preprocesarlas y evaluarlas. En el caso de Ruby, Point, Line, etc, son classes, con métodos que implementan las operaciones.
Esto se refiere a una de las clases, donde Dan habla de como hay una tabla de doble entrada entre entidades (Point, Line, etc.) y operaciones (eval_exp, preprocess, print, simplify). De esta forma, un lenguaje funcional nos va a permitir agregar operaciones fácilmente sin tocar otras partes del programa. Por otro lado, un lenguaje basado en clases nos permite agregar entidades sin impactar el resto del programa.
El problema es agregar entidades en el caso funcional (por ejemplo, un rectángulo). Eso implicaría agregar un branch en todos los pattern matches de las funciones en el caso de SML. O, si queremos agregar una operación nueva (dibujar) en la implementación de Ruby. En este caso, tendríamos que agregar un método a todas las clases.
La otra lección importante de este assignment es sobre el dynamic dispatch de Ruby. Dan no nos permite usar los métodos #is_a? ni #class. Según el , eso sería un híbrido con los pattern match de sml. Entonces, nos obliga a usar dynamic dispatch para implementar Intersect.
La idea de is_a, es saber de que tipo es el argumento de un método. Para evitar usar ese método, dado, que sabemos cual es la clase de self (ja!), si self es una línea (por ejemplo), hacemos
other.intersectLine self
E implementamos intersectLine en todas las clases. Esto evita tener que preguntar la clase de un objeto. (No sé que implica esto para usar duck typing). Para implementar la intersección de dos segmentos, el algoritmo implica convertir uno de los segmentos en una línea. La forma en que tenemos que implementar esta solución usando inheritance y dynamic dyspatch es mind bending…
Pasé más tiempo de lo que esperaba con este problema, pero en el global fue muy interesante.
Examen
Al final del curso hay un cuestionario bastante teórico. Como no pude repasar los videos con cuidado no traté de hacerlo, pero probablemente lo haga en breve.