sábado, 6 de marzo de 2010

Eliminando boilerplate con HOF (higher order functions)

Esta es una pequeña demostración de cómo las HOF permiten eliminar boilerplate. Para iniciados será algo muy obvio, pero puede ser interesante para los que sólo usan lenguajes sin HOF, como Java. Para empezar, definamos:

Boilerplate: secciones de código que es necesario escribir (o copiar y pegar) en diferentes contextos o aplicaciones, casi sin variaciones. Esto puede ser por limitación de un lennguaje o framework. El boilerplate es indeseable porque agranda el código sin aportar nada nuevo, y como todo código está sujeto a errores y es otra fuente de bugs. Idealmente el boilerplate debe ser abstraído.

Higher order functions: son las funciones que pueden recibir funciones como argumento (y eventualmente invocarlas), o retornar funciones como resultado. Es característica básica de todo lenguaje funcional.

El caso que voy a mostrar viene de una aplicación en Scala con SWT; hice un mini wrapper ad-hoc de SWT que no se justifica detallar acá, pero la situación es: tengo un trait TabView para ciertos componentes visuales.
trait TabView {
  def init: Unit
  def reset: Unit = /* reinicializa el TabView */
  def redisplay: Unit = /* despliega el contenido del TabView */
  // etc...
}
El método init debe ser implementado por las vistas concretas, definiendo el contenido visual. En algunas vistas tenía la necesidad de hacer cambios en el contenido visual, pero reflejar los cambios en pantalla no era algo tan directo: lo resolví implementando los métodos reset y redisplay en el trait, y haciendo los cambios de esta manera:
class MyView extends TabView {
  def init: Unit = /* definir contenido visual en base a un modelo */
 
  def changeStuff: Unit = {
    reset
    /* hacer cambios en el modelo */
    init
    redisplay
  }
}
En otras palabras, cada vez que necesitaba cambiar alguna cosa en el modelo que debería reflejarse en la vista, debía antes invocar a reset, y después a init y a redisplay. Eso que siempre se hace de la misma manera y que lleva más de una línea de código, es boilerplate.

Usando HOF podemos resolver esto aunque el código relevante del cambio esté en el medio del código reutilizable. Lo hacemos enviando nuestro código relevante como argumento a un método que sabe cómo renovar la vista, asi nos olvidamos de los pasos necesarios. Este es el método que agregué al trait:
  protected def doWithRefresh(task: => Unit): Unit = {
    reset
    task
    init
    redisplay
  }
Y así es como lo invoco:
  def changeStuff: Unit = doWithRefresh {
    /* hacer cambios en el modelo */
  }
Por si no queda claro, el bloque entre llaves es el argumento de tipo => Unit que pasamos al método doWithRefresh. Éste método ejecuta los pasos necesarios en el orden correcto, incluyendo la invocación a nuestra función anónima.

Para una solución equivalente en Java, tendríamos que definir una interfaz como la siguiente, y que sea éste el tipo aceptado por doWithRefresh():
  interface Task {
      public void execute();
  }
Luego lo usaríamos con una clase anónima que implementa Task y por consiguiente el método execute():
  void changeStuff() {
    doWithRefresh(new Task() {
      public void execute() {
  /* hacer cambios en el modelo */
      }
     });
  }
Esto es lo más cercano que tenemos en Java a HOFs: estamos obligados a definir al menos una interfaz, y a instanciarla con una clase anónima. En Java no existe la función como ciudadano de primera, sólo existe como método en un objeto: estamos condenados a pasar un objeto como argumento. Y esta solución, como se ve, agrega su propio boilerplate. El patrón es bueno y es el que se usa por ejemplo en Spring JDBC para los RowMappers. Pero, como dicen, la necesidad de un patrón suele indicar una falencia del lenguaje, y este es un buen ejemplo de eso. Donde hay soporte de HOF, este patrón no tiene razón de ser.

domingo, 27 de diciembre de 2009

Call Super en API de Android


Un API o un framework bien diseñado debe acotar al máximo las posibilidades de que sus usuarios hagan macanas. No puedo decir que la plataforma Android esté en general mal diseñada, pero ahí encuentro un antipatrón que presentaré como ejemplo.


Android provee la clase Activity, que uno debe subclasear para definir actividades específicas. La clase define varios métodos que deben ser sobreescritos en la subclase.


En el javadoc del método onCreateOptionsMenu leemos:


The default implementation populates the menu with standard system menu items. These are placed in the CATEGORY_SYSTEM group so that they will be correctly ordered with application-defined menu items. Deriving classes should always call through to the base implementation.


¿Hay algo que les haga ruido en esta descripción? Si no, piénsenlo un poco.


Nos dice que la implementación default llena el menú con... bla bla bla. OK, creo que hasta podemos ignorar lo que hace. Pero dice que las subclases deben siempre llamar a la implementación base. La pregunta del millón: si siempre hay que hacer X cosa, ¿por qué arriesgarnos a que se me ocurra no hacerla, o me olvide? Lo que deba hacerse siempre, no debe requerir mi intervención.


Vean si no, cómo se les escapó la llamada a ellos mismos en un ejemplo.


En el tutorial de la Notepad Application aportan a la confusión con el valor de retorno:


@Overridepublic
boolean onCreateOptionsMenu(Menu menu) {
boolean result = super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, 0, R.string.menu_insert);
return result;
}


El javadoc dice que debemos retornar true si se debe mostrar el menú, false en caso contrario. Entonces, ¿por qué retornar lo que diga la implementación base? He visto otros ejemplos en los que se ignora el retorno de la implementación base, y se retorna true. ¿Será que la implementación base retorna siempre true? Estas dudas molestan, entorpecen. No deberíamos estar en situación de hacernos esas preguntas, la culpa es del mal diseño.


En wikipedia me entero que el antipatrón es conocido como Call Super.


Como allí bien se explica, esto se corrige aplicando el patrón Template Method. Activity podría haber definido el método así:



public final boolean baseOnCreateOptionsMenu(Menu menu) {
// ...
return onCreateOptionsMenu(menu);
}
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}


El método baseOnCreateOptionsMenu() no puede ser sobreescrito porque es final. El framework, ante el evento correspondiente, llamaría a éste método, que contiene "la implementación base", y éste a su vez llama a onCreateOptionsMenu(), que sí podemos sobreescribir libremente.


Así nadie mete la pata, y todos contentos.

miércoles, 2 de septiembre de 2009

Anillo inmutable

Se me presentó un caso para el que necesité un tipo de colección que no está en la librería estándar de Java ni la de Scala: una lista circular. Que se la pueda recorrer en ambos sentidos, que no tenga fin. Seguramente hay implementaciones por ahí pero lo tomé como un ejercicio.

Ya me acostumbré a usar estructuras inmutables por default, así que este anillo será inmutable. Primer paso: definir el API. 
trait Ring[T]
{
  def get: T
  def next: Ring[T]
  def previous: Ring[T]
}
Bien simple. La idea es que una instancia de Ring me da acceso al elemento "actual" únicamente, con el método get. Con next y previous obtengo un nuevo Ring, con el mismo contenido, donde el elemento "actual" es el próximo o el anterior respectivamente.

Cualquier implementación de una colección inmutable debe contemplar alguna manera de establecer el contenido. Una alternativa es proveer el contenido en una colección más "básica" como sería un Array[T], donde los elementos están en un orden determinado. El anillo nos da una vista de ese contenido en la que los extremos están unidos. En este caso, el Array, aparte de ser el proveedor de contenido para el anillo, es la estructura subyacente de la implementación: con next y previous se reinstancia Ring pero siempre compartiendo la misma instancia del Array. El trabajo del anillo es escencialmente mantener un índice al elemento actual. Una implementación que promete eficiencia.
class ArrayRing[T](elems: Array[T], curIndex: Int) extends Ring[T]
{
    def this(elems: Array[T]) = this(elems, 0)
    
    if(elems == null) throw new IllegalArgumentException
    if(curIndex < 0 || curIndex >= elems.size) throw new IndexOutOfBoundsException

    def get: T = elems(curIndex)
    
    def next: Ring[T] = new ArrayRing(elems, if(curIndex < elems.size - 1) curIndex + 1 else 0)
 
    def previous: Ring[T] = new ArrayRing(elems, if(curIndex > 0) curIndex - 1 else elems.size - 1) 
}
Al instanciar ArrayRing, se puede indicar qué índice corresponde al elemento actual; si no, se asume el primero (0).

Me vino bien para lo que necesitaba, pero no incluiría esta clase en una librería para terceros porque cometí un sutil pecado. Si lo presento como un anillo inmutable, y de hecho eso se desprende del API, no puedo usar como estructura subyacente una colección mutable (Array) que está fuera de mi control. La clase se comporta como si el Array nunca cambiara, cuando nada garantiza tal cosa. Desde el punto de vista del usuario de la clase, nada revela que el Array se utiliza para algo más que para proveer los elementos al anillo; uno supondría que, una vez instanciado el ArrayRing, éste pierde toda dependencia con el Array informado.

Entonces, hagan lo que yo digo, no lo que yo hago. Una mejor implementación debería pedir una colección inmutable (y en ese caso no importa si se utiliza como estructura subyacente o no), o bien tomar el Array pero pasar sus elementos a alguna otra estructura interna. En éste último caso sería conveniente reutilizar esa estructura interna al reinstanciar el anillo con next y previous, para no perder en eficiencia.

sábado, 30 de mayo de 2009

El backstage de NS-Matic: índice de posts

Ahora que NS-Matic está online gracias a Google AppEngine, les dejo un índice de los posts que podemos considerar backstage del proyecto: desde el parseo de PL/SQL, pasando por la construcción del AST, hasta la generación de diagramas en diferentes tecnologías.

  1. Parseando en Scala (1): Intro 
  2. Parseando en Scala (2): Metasintaxis y AST 
  3. Parseando en Scala (3): Modelo 
  4. Parseando en Scala (4): Combinator parsing en acción 
  5. Parseando en Scala (5): El código 
  6. Parseando en Scala (6): Generando la salida 
  7. Una vuelta de tuerca más a la generación del diagrama 
  8. Superando el desafío de llevar el generador de diagramas a AppEngine 

Superando el desafío de llevar el generador de diagramas a AppEngine


El parseador y generador de diagramas del que hablábamos por acá ahora tiene nombre y está publicado como aplicación web en Google AppEngine: NS-Matic .
Como se sabe, desde hace poco AppEngine soporta Java, y eso significa que soporta varios lenguajes más: todos aquellos que compilan a bytecode para la JVM. Lo que yo no sabía hasta luego del primer deploy, era que las limitaciones impuestas por Google iban a frustrar el plan. Resulta que hay una lista blanca de clases estándar disponibles, entre las cuales no están las de AWT. En AppEngine no se puede instanciar ninguna clase de AWT. Google provee un API alternativo de manipulación de imágenes pero no incluye el tipo de operaciones que mi aplicación necesitaba.
Al no poder generar imágenes en el servidor, consideré usar el viejo generador de HTML. Pero no me quise conformar, e investigué la posibilidad de pasarle el fardo al cliente: ¿se puede dibujar en el navegador?

Dojo al rescate

No estaba al tanto pero resulta que sí, todo navegador que se precie hoy en día tiene capacidad de gráficos vectoriales. La mayoría con SVG, Internet Explorer con VML (cuándo no, cortándose solo). Cómo será la cosa: se está hablando de que esto le vaya ganando terreno a Flash. Para resolver las diferencias entre navegadores, podemos usar una librería cross-browser como Dojo Toolkit , y su módulo Gfx que sirve precisamente para graficar. En mis diagramas, el resultado se ve mejor que los GIF que generaba antes con AWT. Lo que se pierde es la posibilidad de guardar el diagrama como un archivo de imagen, lo que sería más práctico que guardar una página con javascript.

Antes dijimos que estábamos preparados para agregar nuevas implementaciones de generadores de diagramas porque teníamos un diseño extensible. El generador de AWT recorría el AST y daba como resultado un BufferedImage; el nuevo debería recorrer el AST y retornar un String con el script a ser ejecutado por el navegador.

Pero esto nos lleva a un nuevo replanteo: los generadores de AWT y Dojo Gfx, si bien apuntan a tecnologías diferentes (el cómo), tienen un núcleo común: el qué. Antes estábamos mezclando el qué y el cómo, ahora estamos obligados a extraer el qué, si queremos hacer las cosas bien.

El graficador abstracto

La idea sería poder definir en abstracto qué líneas hay que trazar y qué texto hay que desplegar, y en qué posición. Para esto necesitamos un modelo sencillo para generadores de tipo gráfico, que tengan la capacidad de tomar esa especificación y ejecutarla en alguna tecnología.

trait Alignment
case class AlignLeft() extends Alignment
case class AlignCenter() extends Alignment

case class Pos(x:Int, y:Int) {
  def shift(dx:Int, dy:Int) = Pos(x + dx, y + dy)
}
case class Line(from:Pos, to:Pos)
case class TextLine(text:String, anchor:Pos, align:Alignment)

Pos define una posición (x,y), y con el método shift obtenemos una nueva posición con un delta horizontal y otro vertical. El concepto de posición, y la posibilidad de obtener una a partir de otra preexistente, serán de uso intensivo en esta solución. Line y TextLine definen los dos elementos básicos que sirven para armar todo.

class GraphicElement(
    val pos:Pos, 
    val width:Int, 
    val elemHeight:Int,
    val textLines: List[TextLine],
    val lines: List[Line],
    val children: List[GraphicElement]) 
{
  lazy val height: Int = maxY - minY 
  lazy val nextPos = pos.shift(0, height)
  
  private def minY = children match {
    case Nil => pos.y
    case _ => (children map (_.pos.y)) reduceLeft (_ min _) min pos.y
  } 
    
  private def maxY:Int = children match {
    case Nil => pos.y + elemHeight
    case _ =>   ((children map (_.maxY)) reduceLeft (_ max _)) max (pos.y + elemHeight)
  }
}

GraphicElement define un nodo gráfico como contenedor de objetos Line, TextLine, y nodos hijos del mismo tipo. El resultado del proceso será un árbol de nodos de éste único tipo. Para calcular en qué posición vertical se ubica cada elemento, es necesario establecer claramente cuánto espacio vertical ocupa cada uno. Al instanciar un GraphicElement, en elemHeight indicamos la altura "neta" del elemento, es decir, cuánto ocupa en vertical sin tomar en cuenta los nodos hijos. Podría ser cero en el caso de un simple contenedor de nodos. Lo que no indicamos nosotros es la altura "bruta", que incluye la de los hijos. Esa la calcula el campo height. Con el método nextPos vemos que cualquier elemento puede determinar la posición "siguiente" que se puede usar como origen del elemento que le sigue, cuando hay varios al mismo nivel. Y lo hace en términos de la posición propia, desplazada en vertical la altura propia.

Acá vemos una versión recortada del pre-generador para gráficos. El método generate retorna el GraphicElement raíz del árbol.

class GraphicGenerator(totalWidth: Int) 
{
  // ... codigo omitido 
  
  private def layoutText(s:String, p:Pos, w:Int, 
                         align:Alignment):List[TextLine] = // ...etc

  private def makeChildren(nodes:List[Node], prevElem:GraphicElement, 
                           w:Int):List[GraphicElement] = // ...etc
  
  def generate(n:Node):GraphicElement = generate(n, Pos(0,0), totalWidth)
  
  private def generate(n:Node, p:Pos, w:Int): GraphicElement = n match
  {
    case s:Stmt =>       
      val textLines = layoutText(s.text, p.shift(5, STMT_HEIGHT - 5), w, AlignLeft())
      val height = (STMT_HEIGHT - TEXT_HEIGHT) + (TEXT_HEIGHT * textLines.size)
      new GraphicElement(p, w, height, textLines, Nil, Nil)
    case f:Loop =>
      val blockWidth = w - STMT_HEIGHT
      val textLines = layoutText(f.text, p.shift(5, STMT_HEIGHT - 5), w, AlignLeft())
      val height = (STMT_HEIGHT - TEXT_HEIGHT) + (TEXT_HEIGHT * textLines.size)
      val blockPos = p.shift(STMT_HEIGHT, height)
      val children = makeChildren(f.body, GraphicElement.nullObject(blockPos), blockWidth)
      val childrenHeight = GraphicElement.sumHeights(children)
      val blockLines = List(Line(p.shift(0,childrenHeight+height), p.shift(w, childrenHeight+height)),
                            Line(p, p.shift(w,0)),
                            Line(blockPos, blockPos.shift(blockWidth,0)),
                            Line(blockPos, blockPos.shift(0,GraphicElement.sumHeights(children))))
      new GraphicElement(p, w, height, textLines, blockLines, children)

    // ... y el resto de los nodos del AST
  }
}

Como ejemplo vemos la resolución de los nodos Stmt (statement) y Loop. No importan mucho los detalles, pero señalaremos un par de cosas. Usamos el método layoutText cada vez que tenemos que generar texto; no podemos directamente instanciar un TextLine a partir de un String porque puede ocurrir que no nos dé el ancho disponible y haya que wrapear ese String. El método resuelve el wrapping y devuelve una lista de TextLine.
El método makeChildren procesa una lista de Node, invocando generate por cada uno, y determinando la posición de cada uno con nextPos del elemento anterior. Le pasamos como argumento un GraphicElement.nullObject, que es un GraphicElement vacío que sirve sólo para definir la posición inicial de esa secuencia de nodos.

Una vez que nos sacamos de encima todo el trabajo pesado, definamos un marco para las implementaciones gráficas. Estas ya no necesitan conocer el modelo del AST (Node y sus subtipos: Stmt, Loop, etc.) porque sólo trabajarán en términos de objetos GraphicElement.

abstract class GraphicBasedGenerator[T](width:Int) {
  def generate(n:Node):T = {
    val s = new GraphicGenerator(width).generate(n)
    generate(s)
  }
  def generate(ge:GraphicElement):T
}

Esta clase ya resuelve el uso del graficador abstracto que a partir de un Node raíz obtiene un GraphicElement raíz. Queda por definir la generación de un T (tipo que depende del renderizado a usar) a partir de un GraphicElement. Entonces, implementé un graficador para Dojo Gfx como subclase de GraphicBasedGenerator, que es bastante sencillo y no incluyo en el post para no alargarlo más.

jueves, 14 de mayo de 2009

El problema del cuadrado con numeritos

Me topé con un problema irresistible, descripto en inglés en este post. No me haría ninguna gracia tener que resolverlo en una entrevista de trabajo, como fue el caso de aquel blogger: mi primer intento, en la tranquilidad de mi casa, fue lento y torpe. Pero es de esos problemas que, una vez leido el enunciado, no se pueden ignorar: si sos programador y mínimamente te gusta tu profesión, deberás resolverlo. Sugiero que lo lean allá, sin ver soluciones, y... ya sabrán que hacer.

Spoiler Alert

La solución en C del blogger me pareció inteserante por lo concisa, lo mismo la de algunos comentaristas de ese blog, que utilizaron diferentes lenguajes. Cuándo no, la mía es en Scala. Con Scala se puede apuntar a una solución concisa por su poder expresivo, pero no quise descuidar otro aspecto: que el código capture la intención. Es una solución funcional, y con objetos (uso un par de clases). Este es el resultado:

case class Pos(i:Int, j:Int)
{
  def shiftIn = Pos(i - 1, j - 1)
}

case class Square(n:Int)
{
  val highest = (n * n) - 1
  val farEdge = n - 1
  private def isEdge(x:Int) = x == 0 || x == farEdge
  private def isEdge(p:Pos):Boolean = isEdge(p.i) || isEdge(p.j)
  private def isTopOrRight(p:Pos) = p.i == 0 || p.j == farEdge
  private def linearOffset(p:Pos) = if(isTopOrRight(p)) p.j + p.i else (farEdge * 2) + (farEdge - p.j) + (farEdge - p.i)
  private def calc(p:Pos):Int = if(isEdge(p)) highest - linearOffset(p) else Square(n - 2).calc(p.shiftIn)

  def calc(i:Int, j:Int):Int = calc(Pos(i,j))
}


Square es un cuadrado de n celdas de lado. Pos es una posición (i, j). Definimos valores para ayudar en la claridad del código: highest es el valor más alto del cuadrado (que estaría en la celda superior izquierda). farEdge es el índice al "borde lejano"... o sea, el borde horizontal cercano es la fila 0, y el lejano la fila farEdge; igual con los bordes verticales (columnas 0 y farEdge).
Los métodos privados conceptualizan los detalles de la implementación. El método calc, en palabras se describiría así:

Si la posición solicitada corresponde a un borde del cuadrado, tomar el valor más alto del cuadrado y restarle un offset lineal deesa posición respecto al contorno del cuadrado.
Si no, intentar con el cuadrado interno al actual.

El método isEdge nos dice si la posición indicada cae en un borde del cuadrado, es decir, que la fila o la columna sea 0 o sea farEdge.
Lo del offset lineal surge de observar, en el planteo del problema, una característica de todos los "anillos" cuadrados que forman la grilla, desde el borde exterior hasta la celda central (o cuadrado de 2 x 2): Desde la celda superior izquierda, que contiene el máximo valor del cuadrado, podemos recorrer el borde en sentido reloj, y son todos valores decrecientes de uno en uno. Si "enderezamos" ese borde, tenemos un vector donde la primera posición es la más alta. Pero como su contenido es tan obvio, no necesitamos representarlo en memoria. Dado un índice, podemos calcular fácilmente el valor que habría en tal posición: en la 0 está highest, en la 1 está highest - 1, y así. Entonces, la única dificultad es obtener ese offset lineal, el índice al vector imaginario, a partir de una posición bidimensional. Esto lo hace el método linearOffset. Pensándolo un poco, descubrimos que hay dos fórmulas: una aplica cuando la posición está sobre el borde superior o derecho, y la otra si está sobre el inferior o izquierdo.

Cuando la posición pedida no está en el borde del cuadrado actual, procedemos con el cuadrado inmediatamente interior. Este cuadrado interior tendrá 2 celdas menos de lado que el actual. Obsérvese que nunca nos importa si empezamos desde un cuadrado más externo al actual: un cuadrado de 3 x 3 es exactamente lo mismo ya sea que hayamos partido de 3 x 3, o que hayamos partido de uno de 7 x 7 bajando hasta 3 x 3. Por eso el problema se puede resolver en términos de anillos individuales: si no me sirve el actual, me lo saco de encima y reintento con el que sigue, como quien quita las capas de una cebolla.

Lo que tenemos que tener en cuenta al bajar al cuadrado inferior es ajustar la posición, porque esta fue informada como relativa al cuadrado actual. Lo que ahora es (1, 1), para el cuadrado inferior es (0, 0). Ese ajuste lo hace la clase Pos con el método shiftIn.

sábado, 25 de abril de 2009

Y hablando de diseño de APIs...

Hay que ver esta presentación de Joshua Bloch: How to design a good API and why it matters . Es fabulosa.