tag:blogger.com,1999:blog-83524325975677552102024-02-02T06:26:10.169-03:00def main{ programando un cacho }Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.comBlogger22125tag:blogger.com,1999:blog-8352432597567755210.post-14095176741737953002010-03-06T21:30:00.000-03:002010-03-06T21:30:35.245-03:00Eliminando boilerplate con HOF (higher order functions)Esta es una pequeña demostración de cómo las <i>HOF </i>permiten eliminar <i>boilerplate</i>. Para iniciados será algo muy obvio, pero puede ser interesante para los que sólo usan lenguajes sin <i>HOF</i>, como <b>Java</b>. Para empezar, definamos:<br />
<br />
<b><i>Boilerplate</i></b>: 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 <i>boilerplate </i>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 <i>boilerplate </i>debe ser abstraído.<br />
<br />
<i><b>Higher order functions</b></i>: 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.<br />
<br />
El caso que voy a mostrar viene de una aplicación en <b>Scala </b>con <b>SWT</b>; hice un mini wrapper ad-hoc de SWT que no se justifica detallar acá, pero la situación es: tengo un trait <b>TabView </b>para ciertos componentes visuales. <br />
<pre class="brush: scala">trait TabView {
def init: Unit
def reset: Unit = /* reinicializa el TabView */
def redisplay: Unit = /* despliega el contenido del TabView */
// etc...
}
</pre>
El método <b>init </b>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 <b>reset </b>y <b>redisplay </b>en el trait, y haciendo los cambios de esta manera:<br />
<pre class="brush: scala">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
}
}
</pre>
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 <b>reset</b>, y después a <b>init </b>y a <b>redisplay</b>. Eso que <i>siempre se hace de la misma manera</i> y que lleva más de una línea de código, es <i>boilerplate</i>. <br />
<br />
Usando <i>HOF </i>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:<br />
<pre class="brush: scala"> protected def doWithRefresh(task: => Unit): Unit = {
reset
task
init
redisplay
}
</pre>
Y así es como lo invoco:<br />
<pre class="brush: scala"> def changeStuff: Unit = doWithRefresh {
/* hacer cambios en el modelo */
}
</pre>
Por si no queda claro, el bloque entre llaves es el argumento de tipo <i>=> Unit </i>que pasamos al método <b>doWithRefresh</b>. Éste método ejecuta los pasos necesarios en el orden correcto, incluyendo la invocación a nuestra función anónima.<br />
<br />
Para una solución equivalente en <b>Java</b>, tendríamos que definir una interfaz como la siguiente, y que sea éste el tipo aceptado por <b>doWithRefresh()</b>:<br />
<pre class="brush: java"> interface Task {
public void execute();
}
</pre>
Luego lo usaríamos con una clase anónima que implementa <b>Task </b>y por consiguiente el método <b>execute()</b>:<br />
<pre class="brush: java"> void changeStuff() {
doWithRefresh(new Task() {
public void execute() {
/* hacer cambios en el modelo */
}
});
}
</pre>
Esto es lo más cercano que tenemos en <b>Java </b>a <i>HOF</i>s: estamos obligados a definir al menos una interfaz, y a instanciarla con una clase anónima. En <b>Java </b>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 <i>boilerplate</i>. El patrón es bueno y es el que se usa por ejemplo en <a href="http://static.springsource.org/spring/docs/2.0.x/reference/jdbc.html">Spring JDBC</a> para los <a href="http://static.springsource.org/spring/docs/2.5.1/api/org/springframework/jdbc/core/RowMapper.html">RowMappers</a>. 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 <i>HOF</i>, este patrón no tiene razón de ser.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-47371307002918307372009-12-27T04:35:00.005-03:002009-12-27T04:46:44.043-03:00Call Super en API de Android<p><br />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.<br /></p><p><br />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.<br /></p><p><br />En el <a href="http://developer.android.com/reference/android/app/Activity.html#onCreateOptionsMenu(android.view.Menu)">javadoc del método onCreateOptionsMenu</a> leemos:<br /></p><p><br /></p><blockquote>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.</blockquote><br /><p></p><p><br />¿Hay algo que les haga ruido en esta descripción? Si no, piénsenlo un poco.<br /></p><p><br />Nos dice que la implementación default llena el menú con... <i>bla bla bla</i>. 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: <b>si siempre hay que hacer X cosa, ¿por qué arriesgarnos a que se me ocurra no hacerla, o me olvide?</b> Lo que deba hacerse siempre, no debe requerir mi intervención.<br /></p><p><br />Vean si no, cómo se les escapó la llamada a ellos mismos <a href="http://developer.android.com/guide/topics/ui/menus.html#options-menu">en un ejemplo</a>.<br /></p><p><br />En el <a href="http://developer.android.com/guide/tutorials/notepad/notepad-ex1.html">tutorial de la Notepad Application</a> aportan a la confusión con el valor de retorno:<br /></p><pre class="brush: java"><br />@Overridepublic<br />boolean onCreateOptionsMenu(Menu menu) {<br /> boolean result = super.onCreateOptionsMenu(menu);<br /> menu.add(0, INSERT_ID, 0, R.string.menu_insert);<br /> return result;<br />}<br /></pre><br /><p><br />El javadoc dice que debemos retornar <b>true </b>si se debe mostrar el menú, <b>false </b>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 <b>true</b>. ¿Será que la implementación base retorna siempre <b>true</b>? Estas dudas molestan, entorpecen. No deberíamos estar en situación de hacernos esas preguntas, la culpa es del mal diseño.<br /></p><p><br />En wikipedia me entero que el antipatrón es conocido como <a href="http://en.wikipedia.org/wiki/Call_super">Call Super</a>.<br /></p><p><br />Como allí bien se explica, esto se corrige aplicando el <a href="http://en.wikipedia.org/wiki/Template_method_pattern">patrón Template Method</a>. Activity podría haber definido el método así:<br /></p><br /><pre class="brush: java"><br />public final boolean baseOnCreateOptionsMenu(Menu menu) {<br /> // ... <br /> return onCreateOptionsMenu(menu);<br />}<br />public boolean onCreateOptionsMenu(Menu menu) { <br /> return true;<br />}<br /></pre><br /><p><br />El método <b>baseOnCreateOptionsMenu()</b> 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 <b>onCreateOptionsMenu()</b>, que sí podemos sobreescribir libremente.<br /></p><p><br />Así nadie mete la pata, y todos contentos.<br /></p>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-64245278850524481852009-09-02T23:56:00.000-03:002009-09-02T23:56:14.637-03:00Anillo inmutableSe 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.<br />
<br />
Ya me acostumbré a usar estructuras inmutables <i>por default</i>, así que este anillo será inmutable. Primer paso: definir el API. <br />
<pre class="brush: scala">trait Ring[T]
{
def get: T
def next: Ring[T]
def previous: Ring[T]
}
</pre>
Bien simple. La idea es que una instancia de <b>Ring </b>me da acceso al elemento "actual" únicamente, con el método <b>get</b>. Con <b>next </b>y <b>previous </b>obtengo un nuevo <b>Ring</b>, con el mismo contenido, donde el elemento "actual" es el próximo o el anterior respectivamente.<br />
<br />
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 <b>Array[T]</b>, 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 <b>Array</b>, aparte de ser el proveedor de contenido para el anillo, es la estructura subyacente de la implementación: con <b>next </b>y <b>previous </b>se reinstancia <b>Ring </b>pero siempre compartiendo la misma instancia del <b>Array</b>. El trabajo del anillo es escencialmente mantener un índice al elemento actual. Una implementación que promete eficiencia.
<br />
<pre class="brush: scala">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)
}
</pre>
Al instanciar <b>ArrayRing</b>, se puede indicar qué índice corresponde al elemento actual; si no, se asume el primero (0).<br />
<br />
Me vino bien para lo que necesitaba, pero no incluiría esta clase en una librería para terceros porque <b>cometí un sutil pecado</b>. Si lo presento como un anillo <i>inmutable</i>, y de hecho eso se desprende del API, no puedo usar como estructura subyacente una colección <i>mutable </i>(<b>Array</b>) que está <i>fuera de mi control</i>. La clase se comporta como si el <b>Array </b>nunca cambiara, cuando nada garantiza tal cosa. Desde el punto de vista del usuario de la clase, nada revela que el <b>Array </b>se utiliza para algo más que para proveer los elementos al anillo; uno supondría que, una vez instanciado el <b>ArrayRing</b>, éste pierde toda dependencia con el <b>Array</b> informado.<br />
<br />
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 <b>Array</b> 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 <b>next </b>y <b>previous</b>, para no perder en eficiencia.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com3tag:blogger.com,1999:blog-8352432597567755210.post-81081479417922986912009-05-30T22:25:00.000-03:002009-05-30T22:25:27.216-03:00El backstage de NS-Matic: índice de posts<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBEQT2RcyqW8ujN_fICq31PeoNjAQQUV0VqFF_z-R6tT2n0DPhnZkMO0vpqaAYqCyvTWnaQCCl9G6h8RX9-4FhvzKd9JWG25ZQiI630INKfedkcab2LoCtLrJ-Nao1cv_FsqEEBvk75RT6/s1600-h/nsdojo.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBEQT2RcyqW8ujN_fICq31PeoNjAQQUV0VqFF_z-R6tT2n0DPhnZkMO0vpqaAYqCyvTWnaQCCl9G6h8RX9-4FhvzKd9JWG25ZQiI630INKfedkcab2LoCtLrJ-Nao1cv_FsqEEBvk75RT6/s320/nsdojo.jpg" /></a></div>
Ahora que <a href="http://nsmatic.appspot.com/">NS-Matic está online</a> gracias a Google AppEngine, les dejo un índice de los posts que podemos considerar <span class="Apple-style-span" style="font-style: italic;">backstage </span>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.<br />
<br />
<ol>
<li><a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-1-intro.html">Parseando en Scala (1): Intro</a> </li>
<li><a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-2-metasintaxis-y-ast.html">Parseando en Scala (2): Metasintaxis y AST</a> </li>
<li><a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-3-modelo.html">Parseando en Scala (3): Modelo</a> </li>
<li><a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-4-combinator-parsing.html">Parseando en Scala (4): Combinator parsing en acción</a> </li>
<li><a href="http://defmain.blogspot.com/2009/03/parseando-en-scala-5-el-codigo.html">Parseando en Scala (5): El código</a> </li>
<li><a href="http://defmain.blogspot.com/2009/04/parseando-en-scala-6-generando-la.html">Parseando en Scala (6): Generando la salida</a> </li>
<li><a href="http://defmain.blogspot.com/2009/04/una-vuelta-de-tuerca-mas-la-generacion.html">Una vuelta de tuerca más a la generación del diagrama</a> </li>
<li><a href="http://defmain.blogspot.com/2009/05/superando-el-desafio-de-llevar-el.html">Superando el desafío de llevar el generador de diagramas a AppEngine</a> </li>
</ol>
<div>
<br /></div>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-69687815812907250032009-05-30T21:59:00.001-03:002009-05-30T22:02:36.458-03:00Superando el desafío de llevar el generador de diagramas a AppEngine<br />
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: <a href="http://nsmatic.appspot.com/">NS-Matic</a> .<br />
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.<br />
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?<br />
<br />
<span style="font-size: x-large;">Dojo al rescate</span><br />
<br />
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 <a href="http://www.dojotoolkit.org/">Dojo Toolkit</a> , 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.<br />
<br />
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 <span style="font-weight: bold;">BufferedImage</span>; el nuevo debería recorrer el AST y retornar un <span style="font-weight: bold;">String </span>con el script a ser ejecutado por el navegador.<br />
<br />
Pero esto nos lleva a un nuevo replanteo: los generadores de AWT y Dojo Gfx, si bien apuntan a tecnologías diferentes (el <span style="font-style: italic;">cómo</span>), tienen un núcleo común: el <span style="font-style: italic;">qué</span>. Antes estábamos mezclando el <span style="font-style: italic;">qué </span>y el <span style="font-style: italic;">cómo</span>, ahora estamos obligados a extraer el <span style="font-style: italic;">qué</span>, si queremos hacer las cosas bien.<br />
<br />
<span style="font-size: x-large;">El graficador abstracto</span><br />
<br />
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.<br />
<br />
<pre class="brush:scala">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)
</pre>
<br />
<span style="font-weight: bold;">Pos </span>define una posición (<span style="font-style: italic;">x,y</span>), y con el método <span style="font-weight: bold;">shift </span>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. <span style="font-weight: bold;">Line </span>y <span style="font-weight: bold;">TextLine </span>definen los dos elementos básicos que sirven para armar todo.<br />
<br />
<pre class="brush:scala">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)
}
}
</pre>
<br />
<span style="font-weight: bold;">GraphicElement </span>define un nodo gráfico como contenedor de objetos <span style="font-weight: bold;">Line</span>, <span style="font-weight: bold;">TextLine</span>, 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 <span style="font-weight: bold;">GraphicElement</span>, en <span style="font-weight: bold;">elemHeight </span>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 <span style="font-weight: bold;">height</span>. Con el método <span style="font-weight: bold;">nextPos </span>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.<br />
<br />
Acá vemos una versión recortada del pre-generador para gráficos. El método <span style="font-weight: bold;">generate </span>retorna el <span style="font-weight: bold;">GraphicElement </span>raíz del árbol.<br />
<br />
<pre class="brush:scala">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
}
}
</pre>
<br />
Como ejemplo vemos la resolución de los nodos <span style="font-weight: bold;">Stmt </span>(statement) y <span style="font-weight: bold;">Loop</span>. No importan mucho los detalles, pero señalaremos un par de cosas. Usamos el método <span style="font-weight: bold;">layoutText </span>cada vez que tenemos que generar texto; no podemos directamente instanciar un <span style="font-weight: bold;">TextLine </span>a partir de un <span style="font-weight: bold;">String </span>porque puede ocurrir que no nos dé el ancho disponible y haya que wrapear ese <span style="font-weight: bold;">String</span>. El método resuelve el wrapping y devuelve una lista de <span style="font-weight: bold;">TextLine</span>.<br />
El método <span style="font-weight: bold;">makeChildren </span>procesa una lista de <span style="font-weight: bold;">Node</span>, invocando generate por cada uno, y determinando la posición de cada uno con <span style="font-weight: bold;">nextPos </span>del elemento anterior. Le pasamos como argumento un <span style="font-weight: bold;">GraphicElement.nullObject</span>, que es un <span style="font-weight: bold;">GraphicElement </span>vacío que sirve sólo para definir la posición inicial de esa secuencia de nodos.<br />
<br />
Una vez que nos sacamos de encima todo el trabajo pesado, definamos un marco para las implementaciones gráficas. Estas ya no necesitan conocer <a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-3-modelo.html">el modelo del AST</a> (<span style="font-weight: bold;">Node </span>y sus subtipos: <span style="font-weight: bold;">Stmt</span>, <span style="font-weight: bold;">Loop</span>, etc.) porque sólo trabajarán en términos de objetos <span style="font-weight: bold;">GraphicElement</span>.<br />
<br />
<pre class="brush:scala">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
}
</pre>
<br />
Esta clase ya resuelve el uso del graficador abstracto que a partir de un <span style="font-weight: bold;">Node </span>raíz obtiene un <span style="font-weight: bold;">GraphicElement </span>raíz. Queda por definir la generación de un <span style="font-weight: bold;">T</span> (tipo que depende del renderizado a usar) a partir de un <span style="font-weight: bold;">GraphicElement</span>. Entonces, implementé un graficador para Dojo Gfx como subclase de <span style="font-weight: bold;">GraphicBasedGenerator</span>, que es bastante sencillo y no incluyo en el post para no alargarlo más.<br />
<div>
</div>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-13444220819535654842009-05-14T14:30:00.000-03:002009-05-14T14:30:23.216-03:00El problema del cuadrado con numeritosMe topé con un problema irresistible, descripto en inglés en <a href="http://evilstickman.com/blog/archives/126">este post</a>. 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 <a href="http://evilstickman.com/blog/archives/126">allá</a>, sin ver soluciones, y... ya sabrán que hacer.<br />
<br />
<span style="font-size: large;">Spoiler Alert</span><br />
<br />
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:<br />
<br />
<pre class="brush: scala">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))
}
</pre>
<br />
<br />
<b>Square </b>es un cuadrado de <i>n</i> celdas de lado. <b>Pos </b>es una posición <i>(i, j)</i>. Definimos valores para ayudar en la claridad del código: <b>highest </b>es el valor más alto del cuadrado (que estaría en la celda superior izquierda). <b>farEdge </b>es el índice al "borde lejano"... o sea, el borde horizontal cercano es la fila <b>0</b>, y el lejano la fila <b>farEdge</b>; igual con los bordes verticales (columnas <b>0</b> y <b>farEdge</b>).<br />
Los métodos privados conceptualizan los detalles de la implementación. El método <b>calc</b>, en palabras se describiría así:<br />
<br />
<blockquote>
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.</blockquote>
<blockquote>
Si no, intentar con el cuadrado interno al actual.
</blockquote>
<br />
El método <b>isEdge </b>nos dice si la posición indicada cae en un borde del cuadrado, es decir, que la fila o la columna sea <b>0</b> o sea <b>farEdge</b>.<br />
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á <b>highest</b>, en la 1 está <b>highest - 1</b>, 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 <b>linearOffset</b>. 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.
<br />
<br />
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.
<br />
<br />
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 <i>(1, 1)</i>, para el cuadrado inferior es <i>(0, 0)</i>. Ese ajuste lo hace la clase <b>Pos </b>con el método <b>shiftIn</b>.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-81188445388045710462009-04-25T22:44:00.000-03:002009-04-25T22:44:49.294-03:00Y hablando de diseño de APIs...Hay que ver esta presentación de Joshua Bloch: <a href="http://www.infoq.com/presentations/effective-api-design">How to design a good API and why it matters</a> . Es fabulosa.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-86688388960575257242009-04-22T10:53:00.001-03:002009-04-22T11:21:48.213-03:00Leyendo XML en Java sin dolorLeer XML en Java es un verdadero <i>pain in the ass</i>, a diferencia de Scala, que tiene <a href="http://metacircular.wordpress.com/2007/02/04/scala-makes-xml-processing-easy/">soporte nativo para XML</a>. No estoy muy al tanto de librerías que facilitan la tarea, que las debe haber, pero quiero compartir una muy elemental que hice. Las características de su diseño que, creo, la hacen interesante son:
<br />
<ul>
<li><a href="http://defmain.blogspot.com/2009/01/lecciones-funcionales-para-imperativos_30.html">Inmutabilidad</a></li>
<li>Acceso a subnodos por invocaciones encadenadas, evitando NPE ante nodos inexistentes</li>
</ul>
El primer punto es una verdad a medias, porque se encapsula un <b><a href="http://java.sun.com/j2se/1.4.2/docs/api/org/w3c/dom/Node.html">org.w3c.dom.Node</a></b> que podría ser modificado por afuera. Esto se solucionaría clonando el nodo. Esta "librería" es simplemente un <i>wrapper </i>sobre el nodo. Cada instancia es creada para un nodo específico y no se le puede asignar otro; pero el nodo en sí es mutable.<br />
El segundo punto se basa en el <a href="http://en.wikipedia.org/wiki/Null_Object_pattern">patrón Null Object</a>: la idea es prescindir de <b>null </b>en el API para evitar el fatídico Null Pointer Exception. Más abajo lo vemos.<br />
Como dije antes la librería es elemental, y sus limitaciones son:<br />
<ul>
<li>No sirve para "recorrer" el XML sino para obtener elementos conocidos</li>
<li>No accede a los atributos de los elementos</li>
</ul>
No sería nada complicado superar esas limitaciones. Pero no es tanto por la utilidad de la librería que escribo este post sino porque me parece un buen ejemplo de API, las ideas son aplicables a otras cosas. Aparte también ilustra la idea de tener una capa de abstracción sobre una librería existente, cuando los casos de uso son lo suficientemente simples, y se desea que el código refleje claramente la intención sin que uno deba perderse en la complejidad (o torpeza) de la librería original.<br />
<span style="font-size: large;"><br /></span><br />
<span style="font-size: large;">API de XmlNode</span><br />
<br />
Esta es la firma de los constructores:<br />
<pre class="brush: java">
public XmlNode();
public XmlNode(Node node);
public XmlNode(Document doc);
</pre>
El primero es el Null Object, y si bien es un constructor público, lo normal será que sólo se utilice desde la misma clase y no desde afuera. El segundo toma un <b>Node </b>y el tercero un <b>Document</b>. Este último toma el nodo raiz del documento. La idea es que podamos partir de un documento o un nodo cualquiera, y olvidarnos de la diferenciación: lo que tenemos es un árbol de nodos.
<br />
<pre class="brush: java">
public String getName();
public String getValue();
</pre>
<br />
Con estos métodos obtenemos el nombre (tag) y el valor (contenido) del nodo. Ambos retornarán <b>null </b>si es un Null Object.<br />
<br />
<pre class="brush: java"> public XmlNode subNode(String tagName);
</pre>
<br />
Este método en cambio nunca retorna <b>null </b>y permite invocaciones encadenadas. El argumento debe ser un tag de un nodo hijo (inmediatamente relacionado con el actual). Veamos un ejemplo. Si creamos un <b>XmlNode </b>a partir de esto:<br />
<br />
<pre> <a>
<b>
<c>hola</c>
<d>que tal</d>
</b>
</a>
</pre>
<br />
Podremos accederlo así:<br />
<br />
<pre class="brush: java"> String nombre = nodo.getName(); // "a"
XmlNode nodoB = nodo.subNode("b"); // subnodo <b>
XmlNode nodoC = nodo.subNode("b").subNode("c"); // subnodo <c>
String valor1 = nodo.subNode("b").subNode("d").getValue(); // "que tal"
String valor2 = nodoC.getValue(); // "hola"
</pre>
<br />
Y también así:<br />
<br />
<pre class="brush: java"> XmlNode nodoX = nodo.subNode("z").subNode("y").subNode("x");
String valor3 = nodoX.getValue(); // null
</pre>
<br />
Ahí vemos la invocación encadenada con nodos inexistentes. En <b>nodoX </b>nos quedó un Null Object. Para verificar si el <b>XmlNode </b>"está definido" o es nulo, tenemos también el siguiente método (inspirado en el homónimo de <b>Option </b>en Scala):<br />
<br />
<pre class="brush: java"> public boolean isDefined();
</pre>
<br />
Así será más prolijo verificar si el nodo existe, en lugar de ver si <b>getName()</b> o <b>getValue()</b> retornan <b>null</b>.<br />
<br />
<span style="font-size: large;">El código</span><br />
<br />
<pre class="brush: java">import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class XmlNode
{
protected Node node = null;
public XmlNode()
{
}
public XmlNode(Node node)
{
this.node = node;
}
public XmlNode(Document doc)
{
this.node = doc.getDocumentElement();
}
public XmlNode subNode(String tagName)
{
if(node != null)
{
NodeList nl = node.getChildNodes();
for(int i = 0; i < nl.getLength(); i++)
{
Node n = nl.item(i);
if(n.getNodeName().equals(tagName))
{
return new XmlNode(n);
}
}
}
return new XmlNode();
}
public String getName()
{
return !this.isDefined()? null : node.getNodeName();
}
public String getValue()
{
return !this.isDefined()? null : node.getFirstChild().getNodeValue();
}
public boolean isDefined()
{
return this.node != null;
}
}
</pre>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com2tag:blogger.com,1999:blog-8352432597567755210.post-41566617853122489582009-04-04T22:29:00.002-03:002009-04-04T22:34:49.746-03:00Una vuelta de tuerca más a la generación del diagramaEn <a href="http://defmain.blogspot.com/2009/04/parseando-en-scala-6-generando-la.html">el post anterior</a> vimos cómo generar un diagrama Nassi-Schneiderman con tablas HTML. Hicimos un generador abstracto que nos permitiría hacer implementaciones alternativas, es decir, el diseño es modularizable.
<br />
<br />
<pre class="brush: scala">trait Generator[T] {
def nullResult:T
def generate(o:Option[Node]):T = o match {
case Some(x) => generate(x)
case _ => nullResult
}
def generate(n:Node):T
}
</pre>
<br />
En nuestro generador de HTML, el parámetro de tipo fue <span class="Apple-style-span" style="font-weight: bold;">String</span>, naturalmente, ya que generamos HTML con Strings. ¿Habrá sido una exageración parametrizar ese tipo? ¿Se les ocurre un generador que use un tipo diferente a String?
<br />
<br />
¿Y por qué no generar el diagrama como imagen? Un poco de AWT no hace mal. Mi nueva implementación usa <span class="Apple-style-span" style="font-weight: bold;">java.awt.Image</span>. Así es como se ve el nuevo diagrama (a partir del mismo código usado como entrada en el post anterior):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj52YnTq8TacNZohfwDuvHKQgKgXDoGjb9pIIo5QWvsi8890Aw17-91ruJ8Dcl-7YivsiGAf8YzknLPI5WPh0fNoe97QJxnUpRN2gDWf9-1T7zXizWnBVm5iTlvrh8pOlhOnzeaxRQ90IhR/s1600-h/diagram.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj52YnTq8TacNZohfwDuvHKQgKgXDoGjb9pIIo5QWvsi8890Aw17-91ruJ8Dcl-7YivsiGAf8YzknLPI5WPh0fNoe97QJxnUpRN2gDWf9-1T7zXizWnBVm5iTlvrh8pOlhOnzeaxRQ90IhR/s400/diagram.gif" /></a></div>
<br />
Creo que no hace falta explicar nada, el diagrama habla por sí mismo... sólo acotar que, en los IF que no tienen sección ELSE, en vez de dividir el cuadro por la mitad, hice un 90%/10% para evitar que se ocupe mucho espacio con la parte vacía. Faltan algunas mejoras, como incluir el nombre del módulo, los parámetros y la sección de declaraciones; y parametrizar el ancho de la imagen, que ahora está hardcodeado. En cuanto al alto, debería poder ser calculado según el largo del código, pero no lo resolví y por ahora también está hardcodeado.
Definí las siguientes clases auxiliares inmutables, porque esto tiene uso intensivo de "puntos" y "áreas":
<br />
<br />
<pre class="brush: scala">case class Point(x:Int, y:Int)
case class Area(start:Point, end:Point) {
lazy val width = end.x - start.x
lazy val height = end.y - start.y
lazy val middleX:Int = start.x + (width / 2)
lazy val middleTop = Point(middleX, start.y)
lazy val middleBottom = Point(middleX, end.y)
lazy val leftBottom = Point(start.x, end.y)
lazy val leftTop = start
lazy val rightTop = Point(end.x, start.y)
}
</pre>
<br />
Los valores definidos en <span class="Apple-style-span" style="font-weight: bold;">Area </span>son útiles para que el código sea más claro. Si fueran funciones, el código se ejecutaría en cada invocación. Al ser <span class="Apple-style-span" style="font-weight: bold;">val</span>, el código se ejecuta a lo sumo una vez: el valor queda "recordado" para subsiguientes referencias. Y son <span class="Apple-style-span" style="font-weight: bold;">lazy </span>para que no se ejecuten hasta ser referenciados.
<br />
<br />
<pre class="brush: scala">case object Area {
def apply(a1:Area, a2:Area):Area =
Area(Point(a1.start.x min a2.start.x, a1.start.y min a2.start.y),
Point(a1.end.x max a2.end.x, a1.end.y max a2.end.y))
}
</pre>
<br />
Con esto logramos que <span class="Apple-style-span" style="font-weight: bold;">Area </span>pueda ser usado como función, pasando dos áreas como argumentos. El resultado será un área que abarque completamente a esas dos. Esto fue necesario para, luego de procesar ambas secciones de un IF, obtener el área completa de la estructura (ya que cada sección tiene una altura independiente, según el código contenido).
Finalmente, el generador:
<br />
<br />
<pre class="brush: scala">trait ImageGenerator extends Generator[Image] {
def imageWidth:Int
val totalWidth = imageWidth
val imageHeight = 800
def nullResult = null
def generate(n:Node):Image = {
val img = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_BYTE_BINARY)
val g = img.createGraphics
g.setPaint(Color.WHITE)
g.fillRect(0, 0, imageWidth, imageHeight)
g.setPaint(Color.BLACK)
(new GraphicsImageGenerator(g)).generate(n, totalWidth - 20, Point(10,20))
img
}
}
</pre>
<br />
El método abstracto <span class="Apple-style-span" style="font-weight: bold;">imageWidth </span>deberá ser implementado por quien use este trait, para indicar el ancho de imagen deseado. La altura en cambio, viene horriblemente hardcodeada de fábrica, por ahora. Obviamente esto no es todo, el trabajo sucio lo hace otra clase, a la que llamé <span class="Apple-style-span" style="font-weight: bold;">GraphicsImageGenerator</span>. El método <span class="Apple-style-span" style="font-weight: bold;">generate </span>crea la <span class="Apple-style-span" style="font-weight: bold;">Image </span>que vamos a retornar. Inicializa el correspondiente objeto <span class="Apple-style-span" style="font-weight: bold;">Graphics </span>y se lo pasa a una nueva instancia de <span class="Apple-style-span" style="font-weight: bold;">GraphicsImageGenerator</span>. Ésta también define un método <span class="Apple-style-span" style="font-weight: bold;">generate </span>pero requiere argumentos adicionales: aparte del <span class="Apple-style-span" style="font-weight: bold;">Node</span>, necesita saber con qué ancho cuenta para dibujarlo, y a partir de qué punto. Estos valores se calcularán para cada nodo a dibujar, aquí sólo se envían los valores iniciales que corresponden al nodo raíz del AST.
<br />
<br />
Y ahora sí, les dejo esa clase para que escudriñen si tienen ganas.
<br />
<br />
<pre class="brush: scala">class GraphicsImageGenerator(val g:Graphics2D)
{
val lineHeight = 11
val stmtSep = 3
val stmtHeight = 15
val loopThickness = 15
private def wrap(s:String, w:Int) = (new LineBreaker(s, w, g.getFontMetrics)).getSubtrings
private def widthOf(ss:List[String]) = ((ss map g.getFontMetrics.stringWidth) :\ 0)(_ max _)
private def drawRect(a:Area) = g.drawRect(a.start.x, a.start.y, a.end.x - a.start.x, a.end.y - a.start.y)
private def drawLine(p1:Point, p2:Point) = g.drawLine(p1.x, p1.y, p2.x, p2.y)
private def drawJustified(text:String, w:Int, p:Point, drawRect:Boolean)(fx:(Int, Int, Int) => Int) : Area = {
val substrings = wrap(text, w)
var y = p.y + stmtHeight
substrings foreach { s =>
g.drawString( s, fx(p.x, g.getFontMetrics.stringWidth(s), w), y)
y += lineHeight
}
Area(p, Point(p.x + w, y))
}
private def drawLeftJustified(text:String, w:Int, p:Point) = drawJustified(text, w, p, false) {
(x:Int, textWidth:Int, w:Int) => x
}
private def drawRightJustified(text:String, w:Int, p:Point) = drawJustified(text, w, p, false) {
(x:Int, textWidth:Int, w:Int) => (x + w) - textWidth
}
private def drawCentered(text:String, w:Int, p:Point) = drawJustified(text, w, p, true) {
(x:Int, textWidth:Int, w:Int) => (x + (w / 2)) - (textWidth / 2)
}
def generate(n:Node, w:Int, p:Point):Point =
n match {
case stmt:Stmt =>
drawLeftJustified(stmt.s, w, p).leftBottom
case loop:Loop =>
val areaLoop = drawLeftJustified(loop.head, w, p)
val startCode = Point(p.x + loopThickness + 2, areaLoop.end.y + 2)
val loopBodyBottom = generateCode(loop.body, (w - 4) - loopThickness, startCode)
val endPoint = Point(areaLoop.end.x, loopBodyBottom.y)
drawRect(Area(startCode, endPoint))
drawRect(Area(areaLoop.leftTop, endPoint))
Point(p.x, endPoint.y)
case cond:Cond =>
val areaCond = drawCentered(cond.c, w, p)
drawRect(areaCond)
val sepStart = if(cond.cElse.isEmpty) Point(p.x + (w * .9).asInstanceOf[Int], areaCond.end.y) else areaCond.middleBottom
val thenWidth = if(cond.cElse.isEmpty) (w * .9).asInstanceOf[Int] - 4 else (w/2) - 4
drawLine(areaCond.leftTop, sepStart)
drawLine(areaCond.rightTop, sepStart)
val pointThen = generateCode(cond.cThen, thenWidth, Point(p.x + 2, areaCond.end.y + 2))
val pointElse = generateCode(cond.cElse, (w/2) - 4, Point(p.x + 2 + (w/2), areaCond.end.y + 2))
val areaCode = Area(areaCond.leftBottom, Point(areaCond.end.x, pointThen.y max pointElse.y))
drawRect(areaCode)
drawLine(sepStart, Point(sepStart.x, areaCode.end.y))
areaCode.leftBottom
case block:Block =>
val codeEndPoint = generateCode(block.content, w - 4, Point(p.x + 2, p.y + 2))
val endPoint = block.exceps match {
case None => codeEndPoint
case Some(es) => generate(es, w - 4, codeEndPoint)
}
drawRect(Area(p, Point(p.x + w, endPoint.y)))
endPoint
case excepSection:ExcepSection =>
generateCode(excepSection.ee, w, p)
case whenExcep:WhenExcep =>
val areaWhen = drawLeftJustified(whenExcep.e, w - loopThickness, Point(p.x + loopThickness, p.y))
val endPoint = generateCode(whenExcep.body, w, Point(p.x, areaWhen.end.y))
drawLine(p, Point(p.x + w, p.y))
drawLine(Point(p.x + loopThickness, p.y), Point(p.x, p.y + loopThickness))
drawLine(Point((p.x + w) - loopThickness, p.y), Point(p.x + w, p.y + loopThickness))
endPoint
case module:Module => generate(module.body, w, p)
case _ => p
}
private def generateCode(codeList:List[Node], w:Int, p:Point):Point =
(p /: codeList) { (start:Point, c:Node) => generate(c, w, start) }
}
</pre>
<br />
Nota: no incluyo la clase <span class="Apple-style-span" style="font-weight: bold;">LineBreaker</span>, que sirve para cortar un String en varios según sea necesario para que el texto entre en un ancho gráfico determinado.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-8430491525893362122009-04-02T17:55:00.001-03:002009-04-02T18:02:15.965-03:00Parseando en Scala (6): Generando la salidaNuestro propósito era parsear código PL/SQL, cosa que ya vimos en los posts anteriores, y generar como salida un diagrama estructurado.
<br />
<br />
Vamos a usar el siguiente código PL/SQL para probar el experimento. No importa lo que hace, es cualquier cosa que se me ocurrió.
<br />
<br />
<pre>PROCEDURE MYPROC (Param IN NUMBER) IS
AUXVAR VARCHAR2(10) := NULL;
begin
IF Param = 1 THEN
AUXVAR = 'A';
ELSIF Param = 2 THEN
AUXVAR = 'B';
END IF;
if AUXVAR IS NOT NULL
THEN
FOR RECORD IN (SELECT * FROM MYTABLE WHERE FLD = AUXVAR)
LOOP
IF RECORD.EXPIRY <= SYSDATE
THEN
LOG_EXPIRED(RECORD.ID);
END IF;
END LOOP;
END IF;
dbms_output.put_line('END OF MYPROC WITH PARAM = ' || Param);
exception
when others then
dbms_output.put_line('OOPS!');
end;
</pre>
<br />
Y vemos antes que nada el resultado que obtuve, tal como lo veo en el navegador. Como decíamos, es una versión HTML del diagrama Nassi-Schneidermann donde tenemos una representación visual de la estructura lógica. <br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4oBUQF4Gkym4RmNv_1hUGwUJoSHrDYtlltrX8hllW3ViSgXICXe3GbhSoKvwlen-EpsOPBNahYx972jDBkAr54uWnmdKG2Z3b5EWJBibTTGq_quDuG9YaJotiimYJsiZBVi64jCTrpbHR/s1600-h/ns.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4oBUQF4Gkym4RmNv_1hUGwUJoSHrDYtlltrX8hllW3ViSgXICXe3GbhSoKvwlen-EpsOPBNahYx972jDBkAr54uWnmdKG2Z3b5EWJBibTTGq_quDuG9YaJotiimYJsiZBVi64jCTrpbHR/s320/ns.jpg" /></a></div>
<br />
<br />
Cada IF es un recuadro, con un encabezado que indica la condición (seguida de un signo de pregunta), y se divide en dos secciones: por convención, la izquierda corresponde a verdadero y la derecha a falso (THEN y ELSE respectivamente). Se puede ver cómo los IF encadenados con ELSIF quedaron resueltos como IFs anidados. Los ELSE vacíos también se dibujan, con "---". Los ciclos, como el FOR del ejemplo, son recuadros que muestran a la izquierda y en verde la cláusula correspondiente, y a la derecha el código. La sección EXCEPTION se muestra al final del recuadro del bloque debajo de una línea punteada. <br />
<br />
Esto es generado por un módulo a partir del <a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-2-metasintaxis-y-ast.html">AST </a> obtenido por el parseador. Pero el generador de HTML no tiene por qué ser la única opción de generación del diagrama, de hecho ya tengo un generador alternativo que comentaré más adelante. El punto es que para permitir la modularización, armamos una jerarquía de traits. El siguiente trait es el padre de los generadores.
<br />
<br />
<pre class="brush: scala">trait Generator[T] {
def nullResult:T
def generate(o:Option[Node]):T = o match {
case Some(x) => generate(x)
case _ => nullResult
}
def generate(n:Node):T
}
</pre>
<br />
Si recordamos, Node es el supertipo del <a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-3-modelo.html">modelo </a> que estamos usando. El parámetro de tipo, <span class="Apple-style-span" style="font-weight: bold;">T</span>, es necesario porque para las posibles salidas pueden usarse diferentes tipos; de hecho esto es resultado de refactorizar mi código para permitir el generador alternativo en forma elegante. Con el método abstracto <span class="Apple-style-span" style="font-weight: bold;">generate</span>, requerimos que todos los generadores implementen la "transformación" de un <span class="Apple-style-span" style="font-weight: bold;">Node </span>cualquiera (de todos los posibles según el modelo), en algo de tipo <span class="Apple-style-span" style="font-weight: bold;">T</span>. Para la generación HTML ese tipo será String. Agregué una implementación default de generate que, en vez de tomar un <span class="Apple-style-span" style="font-weight: bold;">Node </span>toma un <span class="Apple-style-span" style="font-weight: bold;">Option[Node]</span>. Si se trata de un nodo definido, se deriva el control al método abstracto recién mencionado, y si no, a otro que define cómo se representa algo "vacío" para el tipo <span class="Apple-style-span" style="font-weight: bold;">T</span>. Esto me pareció práctico porque el modelo suele tener relaciones con opcionalidad: así puedo invocar indistintamente la generación de un nodo concreto o de un nodo que puede estar presente o no. Finalmente, el generador que hace todo el trabajo:
<br />
<br />
<pre class="brush: scala">trait HTMLGenerator extends Generator[String] {
def nullResult = ""
def generate(n:Node):String = n match {
case Stmt(s) => s.replace("<","&lt;") + "<br>\n"
case WhenExcep(e,body) => "<p>when " + e + " then<br>\n" +
(body map generate).mkString + "</p>\n"
case ExcepSection(s) => openCell("class=\"excsec\"") +
"<strong>exception</strong><br>\n" +
(for(w <- s) yield generate(w)).mkString +
closeCell
case Block(stmts, exceps) => "<table>" +
openCell +
(stmts map generate).mkString +
closeCell +
(if(exceps.isDefined) generate(exceps.get) else "") +
"</table><br>\n"
case Loop(h,body) => "<table><tr><td class=\"loop\">" +
h +
"</td>\n<td>" +
(body map generate).mkString +
"</td></tr></table>\n"
case Cond(c,ct,ce) => "<table>" +
openCell("colspan=\"2\" class=\"ifhead\"") +
c +
"<big><b> ?</b></big>" +
closeCell +
openCell("class=\"ifthen\"") +
(ct map generate).mkString +
"</td><td class=\"ifelse\">" +
(ce match {
case Nil => "---"
case xs => (xs map generate).mkString
}) +
closeCell +
"</table><br>\n"
case Module(t,n,p,r,v,b) => startHtml + "<h3>" + t + " " + n + "</h3>\n" +
(p match { case Some(x) => generate(x); case None => "" }) + "<br>\n" +
(if(v != Nil) "<table>" + openCell + (v map generate).mkString + closeCell + "</table>" else "") +
"<hr><br>" + generate(b) + endHtml
}
private val startHtml = "<html><head><style type=\"text/css\">\n" +
"table { width: 100%; border: solid thin }\n" +
".comment { color: darkgreen; font-style: italic }\n" +
".ifhead { background-color: moccasin; text-align: center }\n" +
".ifthen { border-top: solid thin; border-right: solid thin; }\n" +
".ifelse { border-top: solid thin; border-left: solid thin; }\n" +
".excsec { border-top: dashed thin; }\n" +
".loop { border-right: solid thin; background-color: MediumAquamarine; }\n" +
"</style></head><body>\n"
private def openCell(modif:String) = "<tr><td " + modif + ">"
private def openCell:String = openCell("")
private val closeCell = "</td></tr>"
private val endHtml = "</body></html>"
}
</pre>
<br />
Definimos <span class="Apple-style-span" style="font-weight: bold;">nullResult</span>, para los nodos opcionales que no estén presentes, como un String vacío. Implementamos generate haciendo pattern matching sobre el nodo recibido. Contemplamos todos los tipos de nuestro modelo y generamos la representación HTML deseada para cada uno. Los detalles de la implementación puede que no sean del todo claros a primera vista, pero la idea sí, es bien simple.
<br />
<br />
El nodo <span class="Apple-style-span" style="font-weight: bold;">Module </span>siempre estará en la raiz del AST, por eso es el encargado de especificar el comienzo y el final del documento HTML. Entre una cosa y la otra, se producirán todas las invocaciones recursivas de generate para los nodos contenidos en el árbol. Los estilos CSS acá están hardcodeados, pero idealmente serían customizables.
<br />
<br />
Con esto finalizamos la serie, aunque me parece que habrá un bonus.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-330409515712382962009-03-08T18:55:00.013-02:002009-05-16T14:04:11.799-03:00Parseando en Scala (5): El códigoA continuación, el módulo para parsear PL/SQL en Scala. Insisto en que no es un parseador completo, pero por ahora me sirve así nomás. Disculpen si ven alguna desprolijidad... más abajo explico algunas cosas.<br />
<pre class="brush: scala">
import scala.util.parsing.combinator.ImplicitConversions
import scala.util.parsing.combinator.syntactical.StdTokenParsers
class PLParser extends StdTokenParsers with ImplicitConversions {
type Tokens = PLLexical
val lexical = new PLLexical
lexical.delimiters ++= Set(";")
lexical.reserved ++= Set("procedure","begin","end","if","then","else","is","as","elsif",
"when","exception","loop","for","while", "function", "declare")
def regularStr: Parser[String] = rep1(ident | stringLit | "is") ^^ {s => s.mkString(" ")}
def stmt: Parser[Stmt] = regularStr <~ ";" ^^ {s => Stmt(s.mkString(""))}
def varDecl:Parser[Stmt] = (regularStr ~ opt("exception")) <~ ";" ^^ {(s:String,e:Option[String]) => Stmt(s.mkString("") + " " + e.getOrElse(""))}
def params: Parser[Stmt] = rep1(ident) ^^ {s => Stmt(s.mkString(" "))}
def loop: Parser[Loop] = "loop" ~> rep1(code) <~ ("end" ~ "loop" ~ ";") ^^ { c => Loop("loop", c) }
def forWhileLoop: Parser[Loop] = ("while" | "for") ~ (regularStr <~ "loop") ~ (rep1(code) <~ ("end" ~ "loop" ~ ";")) ^^ { (t:String, h:String, c:List[Code]) => Loop(t + " " + h, c) }
def cond: Parser[Cond] = (
("if" ~> regularStr <~ "then") ~ rep1(code) ~ rep(("elsif" ~> regularStr <~ "then") ~ rep1(code)) ~ opt("else" ~> rep1(code)) <~ ("end" ~ "if" ~ ";")) ^^ { (sif:String, ifthen:List[Code], elsifs:List[String ~ List[Code]], ifelse:Option[List[Code]]) =>
val startCond = Cond(sif, ifthen, Nil)
buildCond(startCond, elsifs, ifelse)
}
def block: Parser[Block] = ("begin" ~> rep1(code)) ~
opt("exception" ~> rep1(whenExcep)) <~ ("end" ~ opt(ident) ~ ";") ^^ {(s:List[Code], es:Option[List[WhenExcep]]) =>
Block(s, if(es.isDefined) Some(ExcepSection(es.get)) else None) }
def code = stmt | block | cond | loop | forWhileLoop
def whenExcep: Parser[WhenExcep] = ("when" ~> ident <~ "then") ~ rep1(code) ^^ { (e:String,s:List[Code]) => WhenExcep(e,s) }
def excepSection: Parser[ExcepSection] = "exception" ~> rep1(whenExcep) ^^ { ee => ExcepSection(ee) }
def procedure: Parser[Module] = (
("procedure" ~> ident) ~
opt(params) ~
(("is" | "as") ~> rep(stmt)) ~
block) ^^
{(s:String, p:Option[Stmt], v:List[Stmt], b:Block) => Module(MTProcedure, s, p, None, v, b)}
def function: Parser[Module] = (
("function" ~> ident) ~
opt(params) ~
("return" ~> regularStr) ~
(("is" | "as") ~> rep(stmt)) ~
block) ^^
{(s:String, p:Option[Stmt], rt:String, v:List[Stmt], b:Block) => Module(MTFunction, s, p, Some(rt), v, b)}
def anonModule: Parser[Module] = (
("declare" ~> rep(varDecl)) ~ (opt("is" | "as") ~> block)) ^^
{(v:List[Stmt], b:Block) => Module(MTAnonymousBlock, "", None, None, v, b)}
def module: Parser[Module] = anonModule | procedure | function
private def buildCond(c:Cond, elsifs:List[String ~ List[Code]], ifelse:Option[List[Code]]):Cond = elsifs match {
case Nil => Cond(c.c, c.cThen, ifelse getOrElse Nil)
case x::xs => x match {
case i ~ ss => Cond(c.c, c.cThen, List(buildCond(Cond(i, ss, Nil), xs, ifelse)))
}
}
}
</pre>
<br />
Extendemos <a href="http://www.scala-lang.org/docu/files/api/scala/util/parsing/combinator/syntactical/StdTokenParsers.html">StdTokenParsers </a>porque allí se definen algunos Parsers básicos que aprovecharemos. <a href="http://www.scala-lang.org/docu/files/api/scala/util/parsing/combinator/ImplicitConversions.html">ImplicitConversions </a>incluye conversiones útiles para usar el operador <span style="font-weight: bold;">^^</span>.<br />
<br />
El trait StdTokenParsers requiere la definición de <span style="font-weight: bold;">Tokens </span>como subtipo de <a href="http://www.scala-lang.org/docu/files/api/scala/util/parsing/syntax/StdTokens.html">StdTokens</a>:<br />
<br />
<pre class="brush: scala">
trait StdTokenParsers extends TokenParsers {
type Tokens <: StdTokens // ... } </pre>
<br />
<br />
<div>
Podríamos haber declarado que el <span style="font-weight: bold;">type Tokens</span> fuese <a href="http://www.scala-lang.org/docu/files/api/scala/util/parsing/combinatorold/lexical/StdLexical.html">StdLexical</a>, un parser léxico provisto por la librería estándar de Scala. En cambio usamos PLLexical, que es lo mismo pero sin diferenciar entre mayúsculas y minúsculas, porque así es PL/SQL. Ese módulo lo agregué yo al proyecto pero no lo vamos a copiar acá, sólo tengan en cuenta que StdLexical asume código <span style="font-style: italic;">case-sensistive</span>. Si el código a parsear no lo es, hay que meter mano por ahí. </div>
<div>
</div>
<div>
Declaramos un objeto lexical de tipo PLLexical. </div>
<div>
</div>
<div>
Como se muestra, agregamos los delimitadores que corresponda (el punto y coma finaliza sentencias en PL/SQL), y una colección de palabras reservadas. Todas las palabras que yo quiera reconocer al definir los parser combinators deben estar en esta lista para que todo funcione. </div>
<div>
</div>
<div>
Y listo, el resto son todas las funciones para parsear el lenguaje en cuestión como se explicó en<a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-4-combinator-parsing.html"> la entrada anterior</a>. </div>
<div>
</div>
<div>
La definición de <span style="font-style: italic;">regularStr </span>como "cadena regular" es auxiliar, me sirve como base para otros parsers para los que no necesito reconocer la estructura interna: sentencias, declaraciones de variables, y expresiones. Se define como una repetición arbitraria de <span style="font-style: italic;">ident </span>(identificadores, definido en StdTokenParsers), <span style="font-style: italic;">stringLit </span>(literales de cadena, idem) y la palabra reservada "is". </div>
<div>
</div>
<div>
Un sentencia (<span style="font-style: italic;">stmt</span>) se define como un <span style="font-style: italic;">regularStr </span>seguido del delimitador ";". Una declaración de variable también puede ser aceptada como una sentencia con la salvedad de que si define una excepción termina en "exception"... al ser ésta una palabra reservada según indicamos arriba, no entrará como parte de un <span style="font-style: italic;">stringLit</span>. Obsérvese cómo, a pesar de que <span style="font-style: italic;">varDecl </span>es una definición diferente a <span style="font-style: italic;">stmt</span>, en ambos casos generamos un nodo <span style="font-weight: bold;">Stmt</span>, porque no necesitamos mayor detalle en <a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-4-combinator-parsing.html">el modelo</a>. </div>
<div>
</div>
<div>
Si me vinieron siguiendo en esta serie, las piezas deberían estar encajando. Voy a detenerme un poco en la definición de <span style="font-style: italic;">cond</span>. Al principio comentaba los problemas que tenía para parsear el ELSIF en previos intentos. El ELSIF de PL/SQL permite un aspecto "aplanado" a lo que en el fondo son IFs anidados. En un diagrama desearíamos ver ese anidamiento correctamente, por lo tanto debemos reconstruirlo. Así es cómo lo logré: </div>
<br />
<pre class="brush: scala">
def cond: Parser[Cond] = (
("if" ~> regularStr <~ "then") ~ rep1(code) ~ rep(("elsif" ~> regularStr <~ "then") ~ rep1(code)) ~ opt("else" ~> rep1(code)) <~ ("end" ~ "if" ~ ";")) ^^ { (sif:String, ifthen:List[Code], elsifs:List[String ~ List[Code]], ifelse:Option[List[Code]]) =>
val startCond = Cond(sif, ifthen, Nil)
buildCond(startCond, elsifs, ifelse)
}
</pre>
<br />
Lo que vemos a la izquierda del <span style="font-weight: bold;">^^ </span>es simplemente el cuasi-EBNF que describe la estructura de los IF: el bloque THEN es obligatorio (<span style="font-style: italic;">rep1</span>), los ELSIF pueden no existir o ser varios (<span style="font-style: italic;">rep</span>), y el ELSE final es opcional (<span style="font-style: italic;">opt</span>). Para construir el nodo, me valgo de esta función auxiliar recursiva:<br />
<pre class="brush: scala">
private def buildCond(c:Cond, elsifs:List[String ~ List[Code]],
ifelse:Option[List[Code]]):Cond = elsifs match {
case Nil => Cond(c.c, c.cThen, ifelse getOrElse Nil)
case x::xs => x match {
case i ~ ss => Cond(c.c, c.cThen, List(buildCond(Cond(i, ss, Nil), xs, ifelse)))
}
}
</pre>
<br />
Finalmente, les muestro cómo podemos crear una aplicación que invoque el parser:<br />
<pre class="brush: scala">
object PLParserMain extends Application with PLParser {
val theCode = // ... obtener el código de un archivo o la fuente que corresponda
val result = module(new lexical.Scanner(theCode.toString))
result match {
case Success(e, _) => Console.println("OK!\n" )
case Failure(msg, _) => Console.println("[Failure] " + msg)
case Error(msg, _) => Console.println("[Error] " + msg)
}
}
</pre>
<br />
Al extender PLParser, estamos integrando todo lo que ese trait define a nuestro objeto aplicación. Vemos cómo pasar el código al parser de módulo (<span style="font-style: italic;">module</span>, que definimos en PLParser), porque un módulo es lo que esperamos encontrar como primer nivel en el código a parsear, y resultará en un nodo <span style="font-weight: bold;">Module </span>como raíz del AST.<br />
<br />
Claro que hasta acá no hicimos más que parsear y reportar un resultado: OK, falla o error. En el <span style="font-weight: bold;">case Success</span> tenemos disponible el AST completo (en este caso bajo el nombre <span style="font-weight: bold;">e</span>) con el que podemos hacer lo que se nos ocurra.<br />
<br />
Sobre eso, más en <a href="http://defmain.blogspot.com/2009/04/parseando-en-scala-6-generando-la.html">la próxima...</a><br />
<br />
<b>Actualización 16/5/09</b>: La versión actual de PLParser tuvo varias mejoras respecto a la publicada en este post. Ahora el modelo diferencia a las sentencias de las declaraciones de variables y los parámetros. Para eso tuve que, entre otras cosas, definir más símbolos como <b>delimiters</b> aparte del punto y coma. Ya no utilizo esa bolsa de gatos del "regularStr".Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com2tag:blogger.com,1999:blog-8352432597567755210.post-41739975233754540002009-02-25T23:09:00.005-02:002009-03-08T22:13:21.910-02:00Parseando en Scala (4): Combinator Parsing en acciónLeemos en el scaladoc del trait <a href="http://www.scala-lang.org/docu/files/api/scala/util/parsing/combinator/Parsers.html">Parsers</a>:<br /><blockquote>(...) El término "<span class="Apple-style-span" style="font-style: italic;">parser combinator</span>" se refiere al hecho de que estos parsers son construidos a partir de parsers primitivos y operadores de composición, como secuenciación, alternación, opcionalidad, repetición, etc. Un parser primitivo es un parser que acepta o rechaza una porción de la entrada, en base a cierto criterio. (...)</blockquote>En <a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-2-metasintaxis-y-ast.html">la segunda parte de esta serie</a> mencionábamos estas posibilidades de "composición" al definir la sintaxis: que algo se pueda repetir, que algo sea opcional, etc. EBNF utiliza determinados símbolos para esos operadores de composición. Este es <a href="http://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_Form#Another_example">un ejemplo de Wikipedia</a> donde se define la sintaxis para escribir un número en un lenguaje hipotético.<br /><br /><pre><br />number = [ "-" ] , digit , { digit } ;<br /></pre><br /><br />Esta definición se lee así:<br /><ul><li>Opcionalmente se comienza con un guión (signo menos). Los corchetes indican opcionalidad.<br /></li><li>Se sigue con un dígito. La coma indica sucesión de elementos.<br /></li><li>Se sigue con 0 a n dígitos. Las llaves indican repetición arbitraria.<br /></li></ul>Esto asume que en alguna otra parte se definió lo que es un "digit" de la misma manera que aquí se define lo que es un "number".<br /><br />En Scala se traduciría más o menos a algo así:<br /><pre class="brush: scala"><br />def number: Parser[Number] = opt("-") ~ digit ~ rep(digit) // luego completamos lo que falta<br /></pre><br />Este sencillo ejemplo ilustra cómo se traduce en forma bastante directa desde la notación EBNF.<br /><ul><li>Opcionalidad: método <span class="Apple-style-span" style="font-weight: bold;">opt()</span><br /></li><li>Sucesión: símbolo <span class="Apple-style-span" style="font-weight: bold;">~</span> (que en realidad es un método)<br /></li><li>Repetición arbitraria: método <span class="Apple-style-span" style="font-weight: bold;">rep()</span><br /></li></ul>Con una vuelta de tuerca más: el método <span class="Apple-style-span" style="font-weight: bold;">rep1()</span> es similar a rep() pero requiriendo que exista como mínimo una ocurrencia (1 a n). La misma definición se puede simplificar a:<br /><pre class="brush: scala"><br />def number: Parser[Number] = opt("-") ~ rep1(digit) // ...<br /></pre><br />El tipo de retorno es <span class="Apple-style-span" style="font-weight: bold;">Parser[X]</span> donde X es una clase del <a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-3-modelo.html">modelo</a>. En este caso supongamos que se definió la clase Number como parte del modelo.<br /><br />También vamos a usar el símbolo <span class="Apple-style-span" style="font-weight: bold;">|</span> para indicar alternativas. Si contempláramos también el signo + para escribir un número positivo, haríamos así:<br /><pre class="brush: scala"><br />def number: Parser[Number] = opt("-" | "+") ~ rep1(digit) // ...<br /></pre><br />Como se puede ver, combinamos los operadores según nuestra necesidad: opcionalmente habrá un signo, y el signo puede ser - o +.<br /><br />Estamos listos para parsear PL/SQL. Como primer ejemplo veamos la definición del LOOP:<br /><pre class="brush: scala"><br />def loop: Parser[Loop] = "loop" ~ rep1(code) ~ ("end" ~ "loop" ~ ";") // ...<br /></pre><br />Desde luego, esto requiere la definición de <span class="Apple-style-span" style="font-style: italic;">code </span>retornando un <span class="Apple-style-span" style="font-weight: bold;">Parser[Code]</span>, y así con todas las dependencias.<br />Todo lo que sea <span class="Apple-style-span" style="font-style: italic;">whitespace </span>no es necesario especificarlo: entre elementos puede haber cualquier cantidad de espacios, tabs o <span class="Apple-style-span" style="font-style: italic;">line feed</span>s. Observar que como fin del loop usamos <span class="Apple-style-span" style="font-family:'courier new';">("end" ~ "loop" ~ ";")</span>, porque si usáramos <span class="Apple-style-span" style="font-family:'courier new';">"end loop;"</span> estaríamos reconociéndolo solamente si se escribe exactamente así.<br /><br />Sigamos con este ejemplo. Esta definición, así como está, no retorna un <span class="Apple-style-span" style="font-weight: bold;">Parser[Loop]</span>: vamos a completarla. Tenemos que agregar una función (una <span class="Apple-style-span" style="font-style: italic;">closure </span>para mayor comodidad) que levante esos elementos parseados y genere un objeto del modelo, en este caso <span class="Apple-style-span" style="font-weight: bold;">Loop</span>. Pero antes, tenemos que usar variantes del operador <span class="Apple-style-span" style="font-weight: bold;">~</span>. Las variantes <span class="Apple-style-span" style="font-weight: bold;">~></span> y <span class="Apple-style-span" style="font-weight: bold;"><~</span> son equivalentes a <span class="Apple-style-span" style="font-weight: bold;">~</span> en tanto que indican secuencia de elementos, la diferencia está en que<span class="Apple-style-span" style="font-style: italic;"> "señalan" el elemento que nos interesa recuperar</span> a fines de construir un nodo del AST, lo que implica que <span class="Apple-style-span" style="font-style: italic;">la parte no señalada será ignorada</span>. Para armar el nodo <span class="Apple-style-span" style="font-weight: bold;">Loop</span>, lo único que necesito de esa definición es la colección de objetos <span class="Apple-style-span" style="font-weight: bold;">Code</span>. Entonces "señalemos" lo que realmente importa: <br /><br /><pre class="brush: scala"><br />def loop: Parser[Loop] = "loop" ~> rep1(code) <~ ("end" ~ "loop" ~ ";") // esto arroja un List[Code] </pre><br />Ahora sí: <span class="Apple-style-span" style="font-weight: bold;">rep1(code)</span> produce un <span class="Apple-style-span" style="font-weight: bold;">List[Code]</span>, y eso es lo que la closure tomará como entrada. La closure en cuestión es sencillísima:<br /><pre class="brush: scala"><br />{ c:List[Code] => Loop("loop", c) }<br /></pre><br />(Uso el string "loop" para indicar que no es un FOR ni un WHILE, este es un detalle de mi implementación que no tiene mucha importancia).<br />Terminamos juntando la definición de la sintaxis y la closure de generación del nodo con el simpático operador <span class="Apple-style-span" style="font-weight: bold;">^^</span>.<br /><pre class="brush: scala"><br />def loop: Parser[Loop] = "loop" ~> rep1(code) <~ ("end" ~ "loop" ~ ";") ^^ { c => Loop("loop", c) }<br /></pre><br /><span class="Apple-style-span" style="font-style: italic;">Voilà!</span> Y simplificamos la closure dejando que el compilador infiera el tipo del argumento. Todo esto es <span class="Apple-style-span" style="font-style: italic;">type-safe</span>, el compilador chilla si nos equivocamos con los tipos.<br />Insisto, nada de esto es magia, estamos usando una librería estándar. Estos operadores raros son métodos de la clase <span class="Apple-style-span" style="font-weight: bold;">Parser</span>. Y si bien a primera vista el código parece un jeroglífico, es sólo cuestión de aprender el API como un "sublenguaje": todo cobra sentido, y de hecho parsear termina siendo una pavada. Cada elemento se define con una parte declarativa (equivalente a un EBNF), el operador <span class="Apple-style-span" style="font-weight: bold;">^^</span>, y una closure que toma lo necesario de esa declaración para crear un nodo del AST.<br /><br />Para parsear los FOR y WHILE utilizo una definición unificada porque son muy similares, y también genero un nodo <span class="Apple-style-span" style="font-weight: bold;">Loop</span>:<br /><pre class="brush: scala"><br />def forWhileLoop: Parser[Loop] = ("while" | "for") ~ (regularStr <~ "loop") ~ (rep1(code) <~ ("end" ~ "loop" ~ ";")) ^^ { (t:String, h:String, c:List[Code]) => Loop(t + " " + h, c) }<br /></pre><br />Esta declaración produce tres cosas relevantes, tomadas como parámetro por la closure: un String que será "while" o "for", un String con la condición o secuencia del FOR, y la correspondiente <span class="Apple-style-span" style="font-weight: bold;">List[Code]</span>.<br /><br />En <a href="http://defmain.blogspot.com/2009/03/parseando-en-scala-5-el-codigo.html">la próxima entrega</a> muestro el contexto donde se define todo esto y vamos cerrando lo que es parseo, para más adelante empezar a explotar el AST.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-63399750434334934552009-02-21T16:48:00.009-02:002009-03-08T22:09:54.539-02:00Parseando en Scala (3): ModeloRetomando, decíamos que el resultado de parsear es generar un <span class="Apple-style-span" style="font-style: italic;">abstract syntax tree</span> (AST). Vamos a ser un poco más gráficos, para que no queden dudas de que se trata de algo bien facilongo.<br /><br />Si el código PL/SQL a parsear fuera éste:<br /><pre name="code" class="plsql"><br />PROCEDURE myproc IS<br />BEGIN<br />s1;<br />s2;<br />IF cond1 THEN<br /> s3;<br />ELSE<br /> s4;<br /> WHILE cond2 LOOP<br /> s5;<br /> s6;<br /> IF cond 3 THEN<br /> s7;<br /> END IF;<br /> END LOOP;<br /> s8;<br />END IF;<br />END;<br /></pre>...nuestro objetivo lo vemos plasmado en este garabato:<div><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_FHHqPb4ZdC0cA__wAdONToZLMYMykK92YnNNnXXEzCuTaUKHtrUdBGrzExweOel5F3S3W__reqYipO4BKVflZQPcVchIsZtSMTTbAWIzgcHzd3NF6sUoBrJvt2bPU9gp0wXPu4E5rRWa/s1600-h/ast-plsql.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 257px; height: 250px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_FHHqPb4ZdC0cA__wAdONToZLMYMykK92YnNNnXXEzCuTaUKHtrUdBGrzExweOel5F3S3W__reqYipO4BKVflZQPcVchIsZtSMTTbAWIzgcHzd3NF6sUoBrJvt2bPU9gp0wXPu4E5rRWa/s320/ast-plsql.jpg" alt="" id="BLOGGER_PHOTO_ID_5305325898534760770" border="0" /></a>Se ve bien clarita en el árbol la estructura sintáctica del código parseado. El árbol no es más que un grafo, compuesto por nodos y relaciones entre ellos.<br /><br />Podemos identificar distintos tipos de nodos. En el dibujito, cada tipo de nodo está pintado de un color diferente (las sentencias en amarillo, etc.) Las relaciones (líneas) indican los subnodos. Las sentencias son nodos terminales, nunca tienen subnodos.<br /><br />Para armar el AST necesitamos modelar los tipos de nodo, cada uno con sus características. Verán cómo la orientación a objetos convive en armonía con la programación funcional: el modelo es una jerarquía de clases. Todos los tipos de nodo son subclase de un nodo abstracto, pero también identifiqué una abstracción en el medio a la que llamé <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-style: italic;">Code</span></span>, ya veremos por qué. Este sería un pseudo-UML del modelo:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCCn1TmK4pezgRFPBTbryFIWYE9QgoiUJnkkfWBWkIVXKrkCY3B7S6szQopUJMzhsBr8r6EXzM_TBjUn6RKW7-2ZR5w2zWTa9ZuL9S6Hg0QuJ2wqZUjzeoo7-vc0xcAAp4P1Kvx42irKZW/s1600-h/jerarquia-plsql.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 154px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCCn1TmK4pezgRFPBTbryFIWYE9QgoiUJnkkfWBWkIVXKrkCY3B7S6szQopUJMzhsBr8r6EXzM_TBjUn6RKW7-2ZR5w2zWTa9ZuL9S6Hg0QuJ2wqZUjzeoo7-vc0xcAAp4P1Kvx42irKZW/s320/jerarquia-plsql.jpg" alt="" id="BLOGGER_PHOTO_ID_5305326329089040194" border="0" /></a><br /><br />Y en <span class="Apple-style-span" style="font-weight: bold;">Scala</span>:<br /><pre class="brush: scala"><br />abstract class Node<br />abstract case class Code extends Node<br />case class Stmt(s: String) extends Code<br />case class Cond(c: String, cThen: List[Code], cElse: List[Code]) extends Code<br />case class Loop(head:String, body: List[Code]) extends Code<br />case class WhenExcep(e: String, body: List[Code]) extends Node<br />case class ExcepSection(ee: List[WhenExcep]) extends Node<br />case class Block(content: List[Code], exceps:Option[ExcepSection]) extends Code<br />case class Module(typ: ModuleType, name: String, pars: Option[Stmt],<br /> returns: Option[String], vars: List[Stmt], body: Block) extends Node<br /></pre><br />Como se puede ver, ninguna de estas clases define métodos; sólo atributos, que son seteados como parámetros del constructor. Además, son <a href="http://defmain.blogspot.com/2009/01/lecciones-funcionales-para-imperativos_30.html">inmutables</a>. El contenido relevante de cada tipo de nodo está en los parámetros: esto determina qué tipo de relaciones con subnodos puede tener en el grafo.<br /><br />Para las colecciones de elementos, usamos <span class="Apple-style-span" style="font-weight: bold;"><a href="http://www.scala-lang.org/docu/files/api/scala/List.html">List[T]</a></span>, la lista inmutable de la librería estándar de Scala. Para los elementos opcionales, usamos <span class="Apple-style-span" style="font-weight: bold;"><a href="http://www.scala-lang.org/docu/files/api/scala/Option.html">Option[T]</a></span>, cuyos posibles valores son <span class="Apple-style-span" style="font-weight: bold;">Some[T]</span> (con un valor de <span class="Apple-style-span" style="font-weight: bold;">T</span> específico) o <span class="Apple-style-span" style="font-weight: bold;">None</span>. Es mejor que manejar la opcionalidad chequeando por la referencia en <span class="Apple-style-span" style="font-weight: bold;">null</span>, pero no nos vayamos por las ramas. Vamos a comentar un poco el modelo (los que no conozcan PL/SQL, podrán imaginárselo más o menos).<br /><br />El tipo de nodo "sentencia" (<span class="Apple-style-span" style="font-weight: bold;">Stmt</span>) sólo contiene un String, que es la sentencia misma en PL/SQL (no nos interesa la estructura interna, nuestro modelo es simplificado). No puede haber subnodos, como decíamos recién.<br /><br />Las condiciones (<span class="Apple-style-span" style="font-weight: bold;">Cond</span>) son más complejas: tienen un String (que es la condición en sí), una sección para el "then" y otra para el "else". Cada una de estas secciones podría ser, en principio, una colección de sentencias, pero lo cierto es que puede haber cosas más diversas... ¡por eso fue necesaria la abstracción <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-style: italic;">Code</span></span>! Estos "elementos de código" pueden ser varias cosas (las cuatro subclases), pero no cualquier <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-style: italic;">Node</span></span>. El mismo <span class="Apple-style-span" style="font-weight: bold;">Cond </span>es un <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-style: italic;">Code</span></span>, y claro, porque puede haber condiciones anidadas.<br /><br />Podría decirse que la sección "then" debe ser modelada diferente a la sección "else", porque en un <span class="Apple-style-span" style="font-weight: bold;">IF</span> de PL/SQL, la primera es obligatoria y la segunda opcional. Pero el modelo no necesariamente debe reflejar todas las reglas de sintaxis que aplican al parseo. Mi parser va a dar error si falta el "then", pero no si falta el "else"; eso está contemplado. Pero con una List vacía (<span class="Apple-style-span" style="font-weight: bold;">Nil</span>) puedo representar un "else" ausente. Asimismo el modelo tampoco refleja que el "then" tendrá por lo menos un elemento.<br /><br />Con la clase <span class="Apple-style-span" style="font-weight: bold;">Loop </span>modelo los distintos tipos de loop, no me interesa diferenciarlos. Cada uno contendrá un encabezado (tipo de loop y condición), y un contenido que es lista de <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-style: italic;">Code</span></span>.<br /><br /><span class="Apple-style-span" style="font-weight: bold;">WhenExcep </span>es el "catch" de excepciones de PL/SQL: contiene el nombre de la excepción y la lista de <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-style: italic;">Code </span></span>asociada.<br /><br /><span class="Apple-style-span" style="font-weight: bold;">ExcepSection </span>es la sección final (dentro de un bloque) que contiene una sucesión de <span class="Apple-style-span" style="font-weight: bold;">WhenExcep</span>.<br /><br /><span class="Apple-style-span" style="font-weight: bold;">Block </span>es un bloque <span class="Apple-style-span" style="font-weight: bold;">BEGIN </span>/ <span class="Apple-style-span" style="font-weight: bold;">END</span>. Consiste en una lista de <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-style: italic;">Code</span></span>, y opcionalmente una sección de excepciones.<br /><br />Finalmente, <span class="Apple-style-span" style="font-weight: bold;">Module </span>representa, obviamente, un módulo, que puede ser un procedimiento, una función o un bloque anónimo de PL/SQL. Tiene un tipo de módulo, un nombre, opcionalmente parámetros (aquí usamos <span class="Apple-style-span" style="font-weight: bold;">Stmt</span>, no porque los parámetros sean una sentencia, sino por simplificar), opcionalmente un tipo de retorno (aplicable a módulos función), declaraciones / variables (una lista de <span class="Apple-style-span" style="font-weight: bold;">Stmt</span>, también por simplificar), y un <span class="Apple-style-span" style="font-weight: bold;">Block</span>, que es el bloque de código de mayor nivel en el módulo.<br /><br />Para los tipos de módulo uso esta definición:<br /><pre class="brush: scala"><br />abstract class ModuleType<br />case object MTProcedure extends ModuleType { override def toString = "procedure" }<br />case object MTFunction extends ModuleType { override def toString = "function" }<br />case object MTAnonymousBlock extends ModuleType { override def toString = "anonymous block" }<br /></pre><br /><a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-4-combinator-parsing.html">En la próxima</a>: vamos a parsear de una vez.</div>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-56406041093152100282009-02-14T02:07:00.004-02:002009-02-21T17:10:20.400-02:00Parseando en Scala (2): Metasintaxis y ASTDemás está decir que, antes de pretender parsear un lenguaje determinado, tenemos que conocer su sintaxis. Y si la conocemos, debemos ser capaces de expresarla formalmente con una metasintaxis, como podría ser <a href="http://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form#Example">BNF</a> o EBNF. Vamos a poner unos ejemplos del tipo de cosas que podemos definir, y los ilustramos con ejemplos de <span class="Apple-style-span" style="font-weight: bold;">PL/SQL</span>, nuestro lenguaje a parsear.<br /><br /><table border="1"><tbody><tr><td>símbolos</td><td>:= (asignación)</td></tr><tr><td>palabras clave</td><td> PROCEDURE</td></tr><tr><td>sucesión de elementos</td><td> IF (seguido de) condición (seguido de) THEN ...</td></tr><tr><td>elementos alternativos</td><td> En una asignación, del lado derecho de := puede haber un <span class="Apple-style-span" style="font-style: italic;">identificador </span>o un <span class="Apple-style-span" style="font-style: italic;">literal</span></td></tr><tr><td>repetición de elementos (0 a n)</td><td> En la sección de declaración de variables puede no haber ninguna, una, o varias</td></tr><tr><td>repetición de elementos (1 a n)</td><td> En la sección EXCEPTION debe haber al menos un catch (con la forma WHEN excepción THEN ...)</td></tr></tbody></table><br />Con este metalenguaje podemos definir el lenguaje completo. Por si no queda claro, cuando decimos "elemento" podemos estar hablando de definiciones de cualquier nivel en la entrada de texto, desde un carácter "A" hasta un bloque de código BEGIN / END. Es decir, en alguna parte definimos en qué consiste un bloque de código y le ponemos un nombre. Luego podemos definir que un PROCEDURE contiene, además de parámetros opcionales, declaración de variables, etc., un bloque de código. Y como las definiciones pueden ser recursivas, también podremos definir que dentro de un bloque de código puede haber bloques de código. ¿Se va entendiendo lo de "<span class="Apple-style-span" style="font-style: italic;">combinators</span>"?<br /><br />Como decía en el post anterior, los <span class="Apple-style-span" style="font-style: italic;">parser combinators</span> en Scala son un ejemplo de DSL (<span class="Apple-style-span" style="font-style: italic;">domain specific language</span>), ya que "se sienten" como un BNF, o digamos que se presta a una traducción directa desde un BNF. Si no contamos previamente con el BNF del lenguaje que queremos parsear, como para hacer esa traducción, podemos directamente expresar el lenguaje con los <span class="Apple-style-span" style="font-style: italic;">parser combinators</span>. Eso es lo que hice yo en este caso, con la salvedad de que no necesitaba parsearlo en profundidad, sino que para mis fines bastaba con capturar la estructura lógica. Esto simplificó la tarea: no necesito interpretar el contenido de una sentencia simple, para reconocerla me basta con que haya una sucesión de caracteres comunes terminados en punto y coma. No importa si hay una asignación, una llamada a un procedimiento, un query, etc.; las sentencias simples las muestro en mi diagrama tal como vienen.<br /><br />Nuestro cuasi-BNF en Scala no es una mera declaración sino que <span class="Apple-style-span" style="font-style: italic;">es a la vez el código que parsea</span>. Lo que tenemos que agregarle es <span class="Apple-style-span" style="font-style: italic;">qué hacer</span> con las estructuras que va reconociendo. <span class="Apple-style-span" style="font-weight: bold;">El que pensó en renderizar la salida, que se retire por favor.</span> Dijimos que vamos a separar las responsabilidades como corresponde.<br /><br /><span class="Apple-style-span" style="font-size:large;">Con ustedes, el AST</span><br /><br />El objetivo inmediato de parsear debe ser obtener el <span class="Apple-style-span" style="font-style: italic;">abstract syntax tree</span>, es decir, una representación de la estructura parseada. Una vez que conseguimos esto, nos olvidamos del texto de entrada y las reglas del lenguaje. Piensen en los compiladores de <span class="Apple-style-span" style="font-weight: bold;">.NET</span>: la plataforma soporta varios lenguajes, y para cada uno hace falta un parseador diferente. Sin embargo todos conducen a lo mismo, el MSIL, una representación del programa que no mantiene dependencia con el lenguaje de origen. Es un AST. Luego desde el AST generamos la salida deseada. Si nos da el cuero podemos generar<span class="Apple-style-span" style="font-style: italic;"> byte code</span> ejecutable.<br /><br />El AST consistirá en un grafo de objetos de un modelo que debemos definir, según la estructura que necesitamos capturar. Ese modelo se define como una serie de clases "nodo". A medida que el cuasi-BNF parsea y detecta estructuras en el texto, indicaremos también qué nodos instanciar en cada caso.<br /><br />Se está poniendo bueno, <a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-3-modelo.html">sigue en la próxima</a>. Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-68716275304867653962009-02-13T00:57:00.004-02:002009-02-15T23:13:20.825-02:00Parseando en Scala (1): IntroVoy a compartir un ejemplo de programación funcional aplicado en la vida real, para resolver un problema concreto que tuve como desarrollador. Más de una vez me tocó descifrar el fuente de un programa ajeno con una lógica tirada de los pelos (a quién no). Cuando el lenguaje en cuestión es <span class="Apple-style-span" style="font-weight: bold;">PL/SQL</span>, se me complica tener una buena perspectiva de la estructura del código.<br />Allá por los '80 en la facultad me enseñaron los <a href="http://en.wikipedia.org/wiki/Nassi-Shneiderman_diagram">diagramas estructurados de Nassi-Schneiderman</a>. Estos diagramas están buenos para leer código estructurado, y a veces garabateo alguno si tengo que armar una lógica complicada. Pero el código ajeno nunca existe en forma de diagrama, así que estaría bueno tener una herramienta que lo genere. Esa herramienta, además de ser útil, podría ser interesante de programar.<br /><br /><span class="Apple-style-span" style="font-size:large;">Problema #1: Generar el diagrama</span><br /><br />Lo más práctico me pareció generar la salida en HTML con tablas, para ver algo parecido al diagrama Nassi-Schneiderman con un browser. Fue una buena opción. Las principales estructuras a visualizar son los condicionales (IF, THEN, ELSE) y los ciclos (FOR, WHILE, LOOP), y la forma en que se anidan.<br /><br /><span class="Apple-style-span" style="font-size:large;">Problema #2: Interpretar el código original</span><br /><br />Dicho de otra manera: <span class="Apple-style-span" style="font-style: italic;">parsear</span>. En la primera encarnación de la herramienta, en C++, parseaba a lo guapo, por no decir a lo bestia. No es que no se pueda parsear bien con C++, pero terminé con una implementación con limitaciones absurdas. Me metí en un berenjenal. Antes de parsear me veía obligado a toquetear el código de entrada para adecuarlo a ciertos requisitos inexcusables... por ejemplo, si había una cadena de ELSIF's:<br /><br /><pre name="code" class="plsql"><br /> IF cond1 THEN<br /> sentencia1;<br /> ELSIF cond2 THEN<br /> sentencia2;<br /> END IF;<br /></pre><br /><br />...para que fuera parseable, tenía que modificarlo manualmente a:<br /><br /><pre name="code" class="plsql"><br /> IF cond1 THEN<br /> sentencia1;<br /> ELSE<br /> IF cond2 THEN<br /> sentencia2;<br /> END IF;<br /> END IF;<br /></pre><br /><br />Creo que también hice una versión en C# que andaba mejor, pero el problema de fondo no estaba resuelto.<br /><br /><span class="Apple-style-span" style="font-size:large;">Combinator parsing al rescate</span><br /><br />La técnica funcional salvadora fue <span class="Apple-style-span" style="font-style: italic;">combinator parsing</span>, en este caso en Scala. No voy a incluir acá un tutorial de Scala ni voy a explicar cómo está implementada la librería (<a href="http://www.cs.kuleuven.be/publicaties/rapporten/cw/CW491.abs.html">si bien vale la pena empaparse en el tema</a>). Voy a enfocarme en el uso de la librería usando mi proyecto como ejemplo. Van a notar que <span class="Apple-style-span" style="font-weight: bold;">parece un lenguaje con soporte especial para parsing, pero es solo una ilusión</span>: el lenguaje es lo suficientemente flexible, porque permite definir operadores como <span class="Apple-style-span" style="font-weight: bold;">|</span> y <span class="Apple-style-span" style="font-weight: bold;">~</span> en forma de métodos, tiene conversiones implícitas, y un toque de <span class="Apple-style-span" style="font-style: italic;">syntax sugar</span> que lo hace óptimo para crear DSLs (<span class="Apple-style-span" style="font-style: italic;">domain specific languages</span>). <div><br /></div><div>Combinator parsing viene incluido en la librería estándar de Scala, y más que un API, parece un sublenguaje. Su implementación puede resultar un tanto sesuda para el no iniciado; pero su utilización, prometo, está al alcance de cualquier programador.<br /><br />Otro problema que tenían las soluciones anteriores era el acoplamiento entre el parsing y la generación de la salida. Vamos a separar las responsabilidades para que, una vez resuelto el parseo, podamos generar todas las salidas que se nos antoje.<br /><br /><a href="http://defmain.blogspot.com/2009/02/parseando-en-scala-2-metasintaxis-y-ast.html">Continuará</a><br /><br /></div>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-48423394732502940712009-02-07T20:00:00.002-02:002009-02-09T23:49:07.257-02:00Guía para el código testeableSiguiendo con los posts "de referencia", hoy traduzco un resumen de la <a href="http://misko.hevery.com/code-reviewers-guide/">Guía para el Código Testeable</a> utilizada en Google y publicada en el <a href="http://misko.hevery.com/">blog de Miško Hevery</a>. Esta guía consiste en 4 fallas de diseño a evitar, y las señales de advertencia que indicarían que las cometimos. Como siempre, se trata de reglas generales, no para seguirlas dogmáticamente, sino para tenerlas presente y considerar el costo de ignorarlas.<br /><br />Quizás más adelante me ponga a traducir la cosa más a fondo, porque se explica todo con mayor detalle, con argumentos y ejemplos. Mientras tanto, quienes puedan leer inglés, háganse un favor y vayan a <a href="http://misko.hevery.com/code-reviewers-guide/">la fuente</a>.<br /><br /><span class="Apple-style-span" style="font-size:large;">Falla #1: El constructor hace trabajo</span><br /><span class="Apple-style-span" style="font-weight: bold;">Señales de advertencia:</span><br /><ul><li>Se usa el operador <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-family:'courier new';">new </span></span>en el constructor o en la declaración de campos<br /></li><li>Llamadas a métodos static en el constructor o en la declaración de campos<br /></li><li>Cualquier cosa en un constructor más allá de asignación de campos<br /></li><li>Objetos que no queden completamente inicializados una vez terminado el constructor (atención con los métodos <span class="Apple-style-span" style="font-family:'courier new';">initialize</span>)<br /></li><li>Flujo de control (lógica condicional o cíclica) en un constructor<br /></li><li>Se construye un complejo grafo de objetos dentro de un constructor, en lugar de usar una factory o un builder<br /></li><li>Bloques de inicialización<br /></li></ul><br /><span class="Apple-style-span" style="font-size:large;">Falla #2: Hurgar en los colaboradores</span><br /><span class="Apple-style-span" style="font-weight: bold;">Señales de advertencia:</span><br /><ul><li>Se pasan objetos pero nunca son utilizados directamente (sólo para obtener acceso a otros objetos)<br /></li><li>Violación de la ley de Demeter: la cadena de llamadas a métodos recorre un grafo de objetos con más de un punto (.)<br /></li><li>Nombres sospechosos: <span class="Apple-style-span" style="font-family:'courier new';">context</span>, <span class="Apple-style-span" style="font-family:'courier new';">environment</span>, <span class="Apple-style-span" style="font-family:'courier new';">principal</span>, <span class="Apple-style-span" style="font-family:'courier new';">container</span>, o <span class="Apple-style-span" style="font-family:'courier new';">manager</span><br /></li></ul><br /><span class="Apple-style-span" style="font-size:large;">Falla #3: Estado global riesgoso y Singletons</span><br /><span class="Apple-style-span" style="font-weight: bold;">Señales de advertencia:</span><br /><ul><li>Uso de singletons<br /></li><li>Uso de campos static o métodos static<br /></li><li>Uso de bloques de inicialización static<br /></li><li>Uso de registries<br /></li><li>Uso de service locators<br /></li></ul><br /><span class="Apple-style-span" style="font-size:large;">Falla #4: La clase hace demasiado</span><br /><span class="Apple-style-span" style="font-weight: bold;">Señales de advertencia:</span><br /><ul><li>Al describir lo que la clase hace usamos la palabra "y"<br /></li><li>Sería complicado para un nuevo programador leer la clase y rápidamente comprenderla<br /></li><li>La clase tiene campos que son usados solamente en algunos métodos<br /></li><li>La clase tiene métodos static que solamente operan sobre los parámetros<br /></li></ul><br />Miško responde a los lectores que dejan sus comentarios, acá va una muestra interesante:<br /><br /><blockquote>En la falla #3 mencionás que el "uso de registries" es una señal de advertencia. Pero la mayoría de las aplicaciones tienen algunos objetos que necesitan ser accedidos desde todas partes, o al menos en base a algún contexto. ¿Cómo manejaríamos tales objetos sin un registry? (...)</blockquote><br /><br />Y la respuesta:<br /><br /><blockquote>(...) Registry, Context, ServiceLocator, o como quieras llamarlo, es un problema. En primer lugar, ¿cómo obtenés su referencia? ¿Variable global?<br /><br />Las variables globales son problemáticas:<br /><a href="http://misko.hevery.com/2008/08/25/root-cause-of-singletons/">http://misko.hevery.com/2008/08/25/root-cause-of-singletons/</a><br /><a href="http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/">http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/</a><br /><br />Los locators son problemáticos:<br /><a href="http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/">http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/</a><br /><br />Y la solución es Inyección de Dependencias aun si es un objeto ampliamente necesitado:<br /><a href="http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/">http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/</a><br />(...)</blockquote>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-38235143428733895342009-02-03T20:22:00.008-02:002009-02-07T20:08:11.225-02:00Diseño orientado a objetos: The S.O.L.I.D. principlesAcá van los <a href="http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod">principios propuestos por el Tío Bob para un buen diseño orientado a objetos</a>, en versión ultra condensada. No los sigo a rajatabla pero son una guía que conviene tener presente, como mínimo. En <a href="http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/">otro post</a> voy a resumir las reglas de testeabilidad, que son un buen complemento a estos principios (y en parte se superponen).<br /><br /><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="color: rgb(51, 51, 255);">S</span>ingle Responsibility Principle (Principio de Responsabilidad Única)</span><br /><br /><blockquote>Nunca debe haber más de un motivo por el cual modificar una clase.</blockquote><br />Este principio se ve violado por todas partes. Un ejemplo típico es cuando un <span class="Apple-style-span" style="font-family:'courier new';">Servlet </span>hace más de lo que debe. Ya de por sí trabajar directamente con servlets sin un framework que los esconda es una mala idea, pero cuando los usamos, debe ser solamente para dar acceso web a nuestra aplicación: levantar parámetros del request y elementos posteados, invocar las validaciones y servicios que corresponda (en otra capa) y manejar el response.<br /><br /><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="color: rgb(51, 51, 255);">O</span>pen / Closed Principle (Principio Abierto / Cerrado)</span><br /><br /><blockquote>Los elementos de software (clases, módulos, funciones, etc.) deben estar abiertos a la extensión, pero cerrados a la modificación.</blockquote><br />En realidad, no podemos evitar completamente las modificaciones, pero el diseño puede tener en cuenta qué tipo de modificaciones contemplar, y permitir que cierto cambio de comportamiento se resuelva con nuevas implementaciones o subclases. De acá podemos derivar la regla general de "programar a interfaces", que las dependencias sean sobre abstracciones.<br /><br /><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="color: rgb(51, 51, 255);">L</span>iskov Substitution Principle (Principio de Sustitución de Liskov, o "Diseño por Contrato")</span><br /><br /><blockquote>Los módulos que usen referencias a clases base deben poder utilizar objetos de subclases sin saberlo.</blockquote><br />Las subclases deben seguir el comportamiento que se espera de la superclase. La superclase define un contrato. Si las subclases lo respetan, la superclase es sustituible por una subclase, y las subclases son intercambiables. Una regla relacionada con este principio es tratar de que el diseño permita a los usuarios del mismo prescindir del operador <span class="Apple-style-span" style="font-weight: bold;">instanceof</span>.<br /><br /><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="color: rgb(51, 51, 255);">I</span>nterface Segregation Principle (Principio de Segregación de Interfaces)</span><br /><br /><blockquote>Los clientes no deben verse obligados a depender de interfaces que no utilizan.</blockquote><br />Las interfaces "gordas" producen acoplamiento entre clientes que deberían estar aislados. Un cliente no tiene por qué "ver" más de lo que necesita de una clase, entonces debería poder accederla a través de una interfaz particular. Por ejemplo, si tenemos una <span class="Apple-style-span" style="font-family:'courier new';">PavaElectrica</span>, y la interfaz <span class="Apple-style-span" style="font-family:'courier new';">Pava </span>define una dependencia con <span class="Apple-style-span" style="font-family:'courier new';">Agua</span>, puede haber clientes para los cuales la <span class="Apple-style-span" style="font-family:'courier new';">PavaElectrica </span>interesa sólo en tanto que <span class="Apple-style-span" style="font-family:'courier new';">ArtefactoElectrico</span>. Y esos clientes deberían poder reutilizarse en proyectos donde haya otras implementaciones de <span class="Apple-style-span" style="font-family:'courier new';">ArtefactoElectrico </span>pero donde no exista la definición de <span class="Apple-style-span" style="font-family:'courier new';">Agua</span>.<br /><br /><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="color: rgb(51, 51, 255);">D</span>ependency Inversion Principle (Principio de Inversión de Dependencias, también conocido como "Inversión de Control")</span><br /><br /><blockquote>Los módulos de alto nivel no deben depender de módulos de bajo nivel: ambos deben depender de abstracciones. Las abstracciones no deben depender de detalles: los detalles deben depender de las abstracciones.</blockquote><br />El término "inversión" se debe a que este principio propone dar vuelta las dependencias con respecto a la tradición procedural: módulos de alto nivel que llaman a módulos de bajo nivel. En OOP creamos estas <span class="Apple-style-span" style="font-style: italic;">dependencias hardcodeadas</span> cuando usamos el operador <span class="Apple-style-span" style="font-weight: bold;">new</span>. Cuando dependemos de abstracciones, las dependencias pueden ser inyectadas, en un módulo de inicialización o con un framework de <span class="Apple-style-span" style="font-style: italic;">inyección de dependencias</span>.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com1tag:blogger.com,1999:blog-8352432597567755210.post-38517120217014757232009-01-30T17:13:00.004-02:002009-03-08T22:06:53.700-02:00Lecciones funcionales para imperativos (2): InmutabilidadSe puede objetar que la definición de funciones puras en un lenguaje orientado a objetos sólo aplicaría a métodos <span class="Apple-style-span" style="font-style: italic;">static</span>, de los que ponemos en clases de "utilitarios". Y los static no son nada representativos de lo que es el paradigma de objetos. Esta observación parece indicar que el paradigma funcional no se lleva bien con el de objetos, y que cualquier intento de fusión sería forzado y torpe.<br />Las clases más elementales de una aplicación típicamente definen <span class="Apple-style-span" style="font-style: italic;">getters </span>y <span class="Apple-style-span" style="font-style: italic;">setters</span>: el clásico "JavaBean". Los getters no son puros porque pueden retornar algo diferente cada vez. Los setters producen efectos colaterales al cambiar el estado del objeto.<br />Habíamos dicho que <a href="http://defmain.blogspot.com/2009/01/lecciones-funcionales-para-imperativos.html">las funciones puras arrojan un resultado que depende solamente de los parámetros</a>. Como los métodos de instancia se ejecutan en el contexto de un objeto, siempre tienen disponible su referencia, y podemos entender a esa referencia como un parámetro implícito. ¿Por qué, entonces, si un simple getter sólo depende del estado del parámetro implícito (el objeto al que pertenece), no tiene transparencia referencial?<br />El motivo es que la mutabilidad del objeto conspira contra este fin. Para que un getter tenga transparencia referencial, es necesario que el atributo retornado no tenga posibilidad de cambiar: que su valor inicial permanezca durante la vida del objeto. Por ende no debería existir el correspondiente setter:<br /><br /><pre class="brush: java"><br />class Person {<br /> private String name;<br /> private int age;<br /><br /> public Person(String name, int age) {<br /> this.name = name;<br /> this.age = age;<br /> }<br /><br /> public String getName() { return this.name; }<br /> public int getAge() { return this.age; }<br />}<br /></pre><br /><br />De hecho, en la programación funcional pura no existen las variables. Todo valor es inmutable. En Java es fácil crear un primitivo inmutable, para eso utilizamos la palabra clave <span class="Apple-style-span" style="font-weight: bold;">final</span>. Pero para hacer inmutable un objeto, eso no alcanza: <span class="Apple-style-span" style="font-weight: bold;">final </span>garantiza que la referencia no apuntará a otro objeto, pero no impide que mute el estado del objeto referenciado. Para lograr esto tenemos que seguir el patrón del ejemplo de arriba.<br />Para quien haya programado siempre en lenguajes imperativos, la idea de trabajar sin mutabilidad puede parecer un sinsentido. Pero pensemos en algo de uso corriente: la clase <span class="Apple-style-span" style="font-weight: bold;">java.lang.String</span> es inmutable. En Java no podemos modificar un objeto String, y sin embargo podemos utilizar esta clase sin problemas.<br /><br /><pre class="brush: java"><br />String s = " test ";<br />s.trim();<br /></pre><br /><br />En este ejemplo vemos un ingenuo intento de modificar la variable s aplicando el método <span class="Apple-style-span" style="font-style: italic;">trim()</span>. Los que alguna vez cometimos este error, aprendimos que los métodos de String retornan una referencia a un nuevo objeto, el resultado del método. Si no guardamos la referencia, de nada sirvió invocar al método. Java no nos advierte de esta situación porque sus diseñadores siguieron <a href="http://defmain.blogspot.com/2009/01/un-cuestionable-legado-de-c-c.html">el criterio de C y C++</a>: se considera válido invocar a un método y descartar su resultado, porque se asume que se lo quiso llamar como procedimiento, y que sólo nos interesan los efectos colaterales.<br />Otro ejemplo interesante de uso intensivo de inmutabilidad es la librería <a href="http://joda-time.sourceforge.net/">Joda Time</a>.<br />Entonces, resumiendo, lo que normalmente hacemos con mutabilidad, lo podemos lograr invocando funciones que producen un nuevo resultado que consiste en una transformación del valor original.<br /><br />Un objeto inmutable es, por definición, <span class="Apple-style-span" style="font-style: italic;">thread-safe</span>. La inmutabilidad nos da <span class="Apple-style-span" style="font-style: italic;">thread-safety</span> en un sentido absoluto, sin depender de un mecanismo obtuso como el de la "sincronización".<br /><br />Volviendo al ejemplo de la clase <span class="Apple-style-span" style="font-family: 'courier new';">Person</span>, ¿cómo definiríamos un método equivalente a modificar la edad de una persona?<br /><br /><pre class="brush: java"><br />public Person withAge(int newAge) { return new Person(this.name, newAge); }<br /></pre><br /><br />Nada más simple.<br />El objetivo no es aplicar el paradigma funcional indiscriminadamente en un lenguaje como Java, porque no está en su naturaleza. Pero sí se puede tomar conceptos útiles y aplicarlos hasta donde sea razonable. Por ejemplo, podemos hacer una función con <a href="http://defmain.blogspot.com/2009/01/lecciones-funcionales-para-imperativos.html">transparencia referencial</a> cuya implementación usa variables:<br /><br /><pre class="brush: java"><br />public int sumChars(String s) {<br /> int a = 0;<br /> for(int i = 0; i < s.length(); i++)<br /> {<br /> a += s.charAt(i);<br /> }<br /> return a;<br />}<br /></pre><br /><br />En un caso así no hay efectos colaterales, toda la mutabilidad está manejada en variables locales.Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-87525765166930963102009-01-29T19:35:00.003-02:002009-01-29T20:06:07.468-02:00Un cuestionable legado de C / C++Muchos lenguajes diferencian los conceptos de <span class="Apple-style-span" style="font-style: italic;">procedimiento </span>y <span class="Apple-style-span" style="font-style: italic;">función</span>, pero los creadores de <span class="Apple-style-span" style="font-weight: bold;">C</span> decidieron unificarlos, supongo que en un intento de simplificación, llamándolos indistintamente funciones. El procedimiento pasó a ser una función que no retorna ningún resultado (o lo que es lo mismo, con tipo de retorno <span class="Apple-style-span" style="font-weight: bold;">void</span>). La unificación va más allá, en tanto que C permite invocar cualquier función como si no retornara un resultado. Es decir, cuando sólo interesa lo que la función hace y no lo que retorna, se invoca a la función como procedimiento y el resultado es descartado automáticamente. Quien haya intentado esto con otros lenguajes, como <span class="Apple-style-span" style="font-weight: bold;">BASIC</span>, se habrá visto obligado a guardar explícitamente un resultado irrelevante, y probablemente considere feliz la iniciativa de C.<br /><br /><span class="Apple-style-span" style="font-weight: bold;">C++</span> mantuvo el criterio de C, aunque empezamos a hablar de funciones miembro, o métodos. Más tarde llegaron <span class="Apple-style-span" style="font-weight: bold;">Java </span>y <span class="Apple-style-span" style="font-weight: bold;">C#</span>, donde seguimos hablando de métodos. Todos reconocemos que detrás del concepto de método subyacen los de procedimiento y función, pero es como si nos hubiésemos puesto de acuerdo en mantener medio difusa esa distinción.<div><br /></div><div>Idealmente, a una función hay que llamarla por el resultado que arroja y no por lo que hace. La llamada a una función debe poder ocupar el lugar de un valor, una función <span class="Apple-style-span" style="font-style: italic;">es</span> un valor. Por otro lado, a los procedimientos se los llama por lo que hacen. La propuesta es: escribir métodos que son claramente una cosa o la otra, manteniendo los efectos colaterales bajo control. </div>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-3442187365243458192009-01-29T19:13:00.009-02:002009-03-08T22:05:05.681-02:00Lecciones funcionales para imperativos (1): Transparencia referencialLa <a href="http://es.wikipedia.org/wiki/Transparencia_referencial">transparencia referencial</a> es una propiedad de las funciones. Aplica a toda función matemática, y las de los lenguajes de <span class="Apple-style-span" style="font-style: italic;">programación funcional pura</span>. Sin embargo no es una noción ajena a la programación imperativa u orientada a objetos: podemos definir métodos como funciones con transparencia referencial, o "funciones puras". Esta es, quizás, la lección más básica que podemos tomar de la programación funcional, para aplicar en programación imperativa.<br /><br /><span class="Apple-style-span" style="font-weight: bold;">Una función tiene transparencia referencial si su resultado depende única y exclusivamente de los parámetros provistos, y no produce efectos colaterales.</span> La implicancia de esto es que, dado un juego de parámetros, no importa cuántas veces llamemos a la función ni desde dónde, el resultado será siempre el mismo.<br /><br />¿De qué otras cosas podría depender una función que de sus parámetros? De la fecha y hora del sistema, de globales, de valores aleatorios o ingresados por el usuario. En un lenguaje OO, un método también puede depender de atributos mutables, y de métodos sin transparencia referencial, del mismo objeto o de otros. Recordemos además que los <span class="Apple-style-span" style="font-style: italic;">singletons </span>y los elementos <span class="Apple-style-span" style="font-style: italic;">static </span>son globales.<br /><br /><pre class="brush: java"><br />// Funciones con transparencia referencial<br /><br />public String capitalize(String x) {<br /> return x.substring(0,1).toUpperCase() + x.substring(1);<br />}<br />public int foo(int a, int b) {<br /> int r;<br /> if(a % 2 == 0)<br /> r = b + a * 2;<br /> else<br /> r = b * 5;<br /> return r;<br />}<br />public float pi() {<br /> return 3.14;<br />}<br /></pre><br /><br /><span class="Apple-style-span" style="font-size:large;">Efectos colaterales</span><br /><br />Los <a href="http://en.wikipedia.org/wiki/Side_effect_(computer_science)">efectos colaterales</a> son cualquier cambio en el estado del sistema, como podría ser: modificar globales u otros elementos, y operaciones de salida a dispositivos, incluyendo la pantalla. Cualquier cosa que cambie en el "mundo exterior" a la función es un efecto colateral: <span class="Apple-style-span" style="font-weight: bold;">una función con transparencia referencial no genera otra novedad en el mundo que el cómputo de su resultado. </span><br />Ejemplos de efectos colaterales:<br /><br /><pre class="brush: java"><br />public Dog createDog(List<animal> animals) {<br /> Dog dog = new Dog();<br /> animals.add(dog); // altera una estructura que existe fuera de la función<br /> return dog;<br />}<br /></pre><br /><div>Este método produce un efecto colateral al mutar el estado del objeto recibido por parámetro.<br /></div><div><br /><pre class="brush: java"><br />public int foo(int a) {<br /> this.someAttr += a; // altera un atributo del mismo objeto<br /> return a > 0? a * a : a;<br />}<br /></pre><br /></div><div>Este otro método es determinístico en su resultado, pero al mutar un atributo, el estado del objeto no será igual cuando se llame al método varias veces con el mismo parámetro.</div><div><br /><pre class="brush: java"><br />public int sum(int a, int b) {<br /> System.out.println("sum"); // genera salida por pantalla<br /> return a + b;<br />}<br /></pre><br />Este caso parece particularmente inocuo. Es como que para el programa da igual si se imprimió "sum" o no. Sin embargo, consideremos los siguientes programas:<br /><br /><pre class="brush: java"><br />// Programa #1<br />int x = sum(1, 1);<br />System.out.println(x);<br /></pre><br /><pre class="brush: java"><br />// Programa #2<br />int x = sum(1, 1);<br />x = sum(1, 1);<br />System.out.println(x);<br /></pre><br /><br />El resultado de ejecutar uno y el otro es diferente, no es lo mismo imprimir algo una vez que dos veces. La pantalla es parte del "mundo exterior" sobre el que actuó la función.<br />La propuesta de hacer funciones sin efectos colaterales puede sonar ridícula, y de hecho no puede haber un programa útil sin efectos colaterales. La idea es que estos efectos estén controlados. Cuando hacemos una función, conviene plantear si los efectos colaterales son necesarios, y si no lo son, quizás sea candidata a tener transparencia referencial.<br />Si determinamos que cierto efecto colateral es necesario, igualmente conviene en lo posible cumplir con el otro aspecto de la transparencia referencial: que el resultado dependa sólo de los parámetros.<br /><br /><span class="Apple-style-span" style="font-weight: bold;">La transparencia referencial ayuda a que el código sea más claro y testeable.</span><br /><br /></div><div>Los efectos colaterales tienen mayor sentido en los procedimientos. A los procedimientos se los llama para hacer algo, para cambiar el estado del sistema, para producir un efecto; en cambio una función es un valor.<br />Los programas en lenguajes de programación funcional consisten en una composición de funciones. En lenguajes imperativos, podemos tener módulos basados en composición de funciones puras, aunque el programa sea impuro. Todo lo que se pueda acotar con una implementación pura será más mantenible y menos proclive a bugs.</div>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-18410060298726615852009-01-27T23:49:00.008-02:002009-01-31T15:31:20.977-02:00Qué cuernos es Scala y por qué esa obsesiónYa lo dije en otras partes pero me voy a tomar un post acá para resumirlo. <a href="http://www.scala-lang.org/">Scala </a>es un lenguaje de incipiente adopción, señalado a veces como "<span class="Apple-style-span" style="font-style: italic;">el próximo Java"</span> o como uno de sus herederos. Aparentemente Java llegó a un techo en su evolución, por cuestiones intrínsecas (su diseño) y extrínsecas (determinación de Sun). Una forma elegante de evadir completamente este problema es pasándose a otro lenguaje mejor diseñado, sin cambiar de plataforma. Scala es uno de esos. Incorpora bastante más que lo que algunos están reclamando como torpe mejora en Java.<div>Algunas características:</div><div><br /></div><div><ul><li>Compila a bytecode (JVM)</li><li>Es interoperable con Java (o sea, se pueden seguir usando librerías y frameworks existentes en Java)</li><li>Es de tipo estático</li><li>Inferencia de tipos</li><li>Fusiona exitosamente programación funcional con OOP</li><li>Tiene una forma de herencia múltiple llamada composición mixin<br /></li><li>"Conversiones implícitas": permiten extender librerías externas y hacer una especie de prototyping</li><li>Incluye un intérprete ("REPL")</li><li>Soporta XML como un tipo más</li><li>Viene con librerías interesantísimas: concurrencia tipo Erlang, parser combinators, etc.</li><li>¿Qué más querés?</li></ul><div>El sistema de tipos tiene cierta complejidad, que domino a medias, pero con eso en general alcanza. Más allá de todas las ideas incorporadas, tomadas de varios lenguajes e integradas por <a href="http://lamp.epfl.ch/~odersky/">Martin Odersky</a> en Scala, el reto mayor para quien viene de Java u otro lenguaje imperativo es asimilar el estilo de <a href="http://es.wikipedia.org/wiki/Programaci%C3%B3n_funcional">programación funcional</a>. </div><div><br /></div><div>Scala permite codificar en estilo imperativo, como si fuera un Java con diferente sintaxis. Eso puede estar bueno para ir metiéndose de a poco, pero de nada servirá si uno no busca avanzar a un código más funcional. El estilo funcional lamentablemente estuvo siempre muy divorciado de la industria y muy casado con la academia, pero esto ya está cambiando. Está presente en Python, Ruby, JavaScript; entró de lleno en .NET con C#/LINQ y F#, etc., y es probable que esta tendencia se profundice, aunque haya que terminar desechando Java por completo.</div><div><br /></div><div>Se dice por ahí que <span class="Apple-style-span" style="font-style: italic;">"aprender programación funcional nos convierte en mejores programadores aunque nos dediquemos a la programación imperativa"</span>. Hay dos o tres cosas que me gustaría rescatar en ese sentido, para un próximo post, aplicables a cualquier lenguaje. Son cosas bien sencillas que se nos escaparon por mucho tiempo, y pueden ser un buen punto de entrada a los beneficios del paradigma funcional.</div><div>¡Salud!</div></div>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0tag:blogger.com,1999:blog-8352432597567755210.post-63467336738270733252009-01-27T22:24:00.016-02:002009-03-08T22:00:19.094-02:00Hola MundoA ver...<br /><pre class="brush: scala"><br />object MyApp {<br /> def main(s:Array[String]) = println("Hello World")<br />}<br /></pre><br />Buenísimo, <a href="http://quoiquilensoit.blogspot.com/2008/02/scala-code-in-blogs.html">encontré cómo formatear código Scala para Blogger</a>. Ahora vamos a ver qué puede salir de esto.<div><br /></div><div>UPDATE: me pasé a la versión 2.0 de <a href="http://alexgorbatchev.com/wiki/SyntaxHighlighter">SyntaxHighlighter</a>, ya viene con soporte de Scala.</div><div><br /></div>Germánhttp://www.blogger.com/profile/07765922715981210093noreply@blogger.com0