Domain Specific Languages em ação
Em diversos momentos sentimos a necessidade de utilizar uma linguagem para atacar um problema mais específico. Utilizar Java ou C# nesse tipo de problema pode gerar uma enorme quantidade desnecessária de código. Veja um exemplo que passamos na Caelum:
Set<Strategy> strategies = new HashSet<Strategy>(); Indicator<Double> close = new ClosePriceIndicator(timeSeries); for (int i = 1; i <= 50; i++) { Indicator<Double> tracker = new EMAIndicator(close, i); Strategy strategy = new IndicatorCrossedIndicatorStrategy(close, tracker); strategies.add(strategy); }
No nosso caso, esse trecho de código deve ser compreensível para analistas de negócio, que não são necessariamente programadores, muito menos possuem conhecimento de Java. Domain Specific Languages é o nome dado a prática de se criar pequenas linguagens para resolver um problemas bem específicos. Elas existem em dois sabores: as externas, que criam uma linguagem própria, e as internas, que na verdade utilizam um subconjunto de instruções de um linguagem já existente e utilizada no sistema.
Entre alguns clássicos exemplos de DSLs internas temos o uso da Criteria do hibernate, o uso do ruby nos arquivos de build do rake. Entre as DSL externas, temos as macros do excel e o xml do ant.
Utilizando a api de scripting do java 6, passamos a usar ruby (através da JRuby) para escrever essa parte da lógica de negócios, e nosso código em java ficou assim:
(1..50).collect{|x| Tail::IndicatorCrossedIndicatorStrategy.new(close, Tail::EMAIndicator.new(close, x)) }
Criando algumas factories, conseguimos chegar a um código muito mais simples:
(1..50).collect{|x| cross(close, ema(x)) }
Logo, estamos próximos de chegar a algo parecido com uma linguagem natural:
x de 1 a 50 quando cruzar (fechamento, ema(x))
Para tal, uma das possibilidades seria usar um dos compiladores de compiladores existentes para Java, porém isso daria muito trabalho.
O Rodrigo Kumpera sugeriu escrever o próprio parser, como fez o Gilad Bracha no Small Talk. Tanto o Rodrigo quanto o Renato Lucindo citaram o spirit++, que faz isso para C++. Uma pena não existir algo equivalente para o Java.
Phillip Calçado recomendou fazer um teste para saber se devemos ou não melhorar ainda mais essa DSL. O teste consiste em colocar a linguagem natural ao lado da DSL ruby e ver se o especialista consegue fazer a ponte entre uma e outra. Exemplo:
(1..50).collect{|x| cross(close, ema(x)) }
x de 1 a 50 quando cruzar (fechamento, ema(x))
Se o especialista no domínio não entender a semelhança entre os dois códigos, é necessário aprimorar a DSL em questão.