Qué debemos considerar antes de crear nuestro DSL

Hoy todos reconocemos el potencial que tiene (LOP) Language Oriented Programming, pero no solo eso sino que muchos ya están invirtiendo para hacerse con las ventajas prometidas por este paradigma(?). Muchos incluso diseñan la sintaxis de aquellos lenguajes que entienden, pueden hacerles alcanzar la productividad, calidad y mantenibilidad que buscan.

Ahora bien, una vez decididos a crear el lenguaje (textual) propio para un dominio particular, hay que implementarlo. Aquí es donde deben estudiarse seriamente las alternativas disponibles para alcanzar la mejor solución dado las restricciones que tenemos (tiempo, presupuesto, conocimientos de ingeniería, etc)

Antes de continuar, quiero aclarar mi definición de DSL: lenguaje pequeño para solucionar un conjunto de problemas de un dominio particular. Esta aclaración es necesaria porque con ella dejamos afuera lenguaje  como Transact SQL el cual si bien es específico, no es para nada pequeño.

Lo que debemos considerar

DSL interno o DSL externo

Este puede ser un muy buen punto de partida. ¿Queremos un DSL externo o uno interno? Antes de que veamos cómo podemos respondernos esta pregunta debemos entender lo que es un Business Natural Language (BNL). Un BNL es un DSL externo tan cercano al lenguaje natural del negocio que manejan los clientes que ellos mismos, con solo un poco de entrenamiento y soporte, son capaces utilizarlos para especificar los requerimientos y brindárselos al sistema el cual los interpreta y ejecuta. La pregunta ahora sería: ¿es esto lo que queremos, un BNL? ¿Debería un cliente poder utilizarlo?

Si es esto lo que buscamos necesitaremos con alta probabilidad un DSL externo. Un ejemplo de un BNL puede encontrarse en mi entrada [DSL] Domain Specific Languages – Un ejemplo. Por el contrario, si lo que buscamos es un medio de elevar el nivel de abstracción en el que estamos programando, un medio por el cual ser más productivos en nuestro lenguaje habitual, probablemente un DSL interno es la solución más conveniente y económica. Un ejemplo de un DSL interno (o Fluent Interface) puede encontrarse en mi entrada Fluent Interfaces y TDD.

Otras señales importantes a tener en cuenta a la hora de decidir si necesitamos un interno o uno externo es la complejidad de las estructuras. Un DSL externo no tiene ninguna restricción mientras que uno interno está limitado a las estructuras del lenguaje que manejamos. Los DSL internos mantienen el ruido sintáctico del lenguaje huésped mientras que los externos no tienen este inconveniente (más allá que cierto ruido hace al parser más sencillo y nos evita el tener un análisis semántico complejo).

¿Declarativo o Imperativo?. Esta es otra clave. Si el lenguaje es si es declarativo no importa demasiado la elección mientras que si el lenguaje es imperativo la opción a elegir debe ser el DSL externo.

Xml, parser artesanal, expresiones regulares o generador de parsers

Este punto también es clave. ¿Podemos conformarnos con especificar el comportamiento mediante el uso de un XML?. Si bien usar xml es algo espantosamente aberrante, muchas veces es la opción más sencilla y económica. Lo único a intentar es lograr un esquema lo menos ruidoso posible.

<listaDePersonas>
     <persona>Lucas</persona>
     <persona>Pablo</persona>
     <persona>Noelia</persona>
     <persona>Santiago</persona>
<listaDePersonas>

Otra alternativa es hacer un parser “a lo tonto” mediante splits por líneas y caracteres especialmente ubicados para servir de delimitadores. Por ejemplo, es muy sencillo hacer un parser rudimentario para obtener la lista de personas del código siguiente:

ListaDePersonas
      Lucas
      Pablo
      Noelia
      Santiago
FinListaDePersonas

Tampoco hay que descartar las expresiones regulares mediante RegEx para parsear un string. Como ejemplo de un lenguaje en el que su creador no sabía nada de nada de creación de parsers y que sin embargo logró su cometido con cierta elegancia pueden ver el código del parser del lenguaje E# del proyecto NBusiness en codeplex. Esto demuestra que es posible hacerlo con expresiones regulares a lo bruto.

Por último tenemos que analizar los generadores de parsers. Existen muchísimos y cada uno, como siempre, tiene sus pros y sus contras. Esta alternativa es mi preferida pero la recomendaría solo para casos en que realmente se justifique su uso. Esto podría ser cuando la complejidad y tamaño de la sintaxis del lenguaje es lo suficientemente grande y se necesita un buen control de errores y recuperación, etc.

Suena tonto pero estudiar Antlr, por ejemplo, es un tanto arduo y requiere mucha práctica y eso es solo para tener un parser, luego falta todo el resto.

Generador de código (fuente/binario), interprete o traductor

Está bien, ya tenemos resuelto lo de arriba entonces una vez que tenemos nuestro lenguaje y nuestro Modelo semántico (DSL interno) o el AST + SymTable (DSL externo) debemos decidir entre estas tres estraetgias. Si estamos hablando de un DSL interno seguramente será ejecutado por un componente del mismo sistema pero tampoco nada impide que genere código en el mismo lenguaje o en otro.

Por otra parte, si es externo, hay que ver como se lo va a usar. Si por ejemplo se utiliza embebido lo más probable es que se lo interprete como por ejemplo podemos ver abajo:

var personas = LangInterpreter.Parse(
     «Lucasn» +
     «Pablon» +
     «Noelian» +
     «Santiago»);

La generación de código fuente, si bien es horrible, tiene algunas ventajas como lo son las posibilidad de retirar el lenguaje y dejarlo sin mantenimiento sin afectar el proyecto ya que no mantenemos código en otro lenguaje sino que lo tenemos en nuestro lenguaje de trabajo obtenido de la generación a partir del código del DSL. El código generado por el generador de código suele ser preferible en formato binario salvo las excepciones mencionadas arriba.

La traducción no es común cuando hablamos de DSLs, en realidad no tiene sentido.

Costos y Beneficios

Muchos quieren subirse a este caballo sin analizar sus costos y sus beneficios. Esto mismo nos puede dar un nivel de riesgo para la creación de un lenguaje nuevo. Podemos decir que un DSL externo, de cierta complejidad, imperativo, desarrollado con Antlr y que genere código a partir de plantillas es caro, caro comparado con un DSL interno, sencillo, declarativo, desarrollado con el mismo lenguaje que se está usando y que por lo tanto es ejecutado como una sentencia más de código.

Hay que considerar que muchos desarrolladores, sino la mayoría, nunca han desarrollado un lenguaje y que muchos de ellos aprobaron la materia Compiladores sin entender realmente la diferencia entre los parsers LL y LR. Por esta misma razón hay que tener en cuenta que los lenguajes deben ser mantenidos y que encontrar desarrolladores capaces de hacerlo no es tarea sencilla.

Espero que esto ayude a pensar a quienes están pensando en crear un DSL sobre las distintas alternativas que tienen a su disposición.

Sin categoría

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *