Estive na apresentação de Glenn Vanderburg, no QCon London 2009, que falou sobre o Estado da Arte da DSL em Ruby. A apresentação e os slides você pode conferir aqui no site da infoq.
A idéia de fazer uso de uma DSL interna originou-se aparentemente no Lisp. Em Lisp você não escreve seu programa apenas direcionado na linguagem, você também constrói a linguagem em cima do seu programa:
(task “warn if websites is not alive ”
every 3 seconds
starting now
when ( not (website-alive? “http://example.org”))
then (notify “admin@example.org”, “server down!”))
)
DSL interna também foi um objetivo de design do Haskell
keepleft (p :>: ps)
| keepleft p = case partitionFL keepleft ps of
a :> b -> p:>: a :>: b
| otherwise = case commuteWhatWeCanFL (p :> ps) of
a :> p’ :> b -> case partitionFL keepleft a of
a’ :> b’ -> a’ :> b’ +>+ p’ :>: b
Agora falando em Ruby, uma das principais características da linguagem é a expressividade. O japonês Yukihiro Matsumoto (Matz), criador da linguagem, sempre teve como objetivo fazer o ruby extremamente legível. Para atingir esse objetivo a linguagem tem o recurso conhecido como “Sintax Sugar”. A performance da linguagem não foi o objetivo inicial, e sim a clareza. Por isso o ruby é uma linguagem mais lenta. Entretanto agora com a adoção maior nos projetos de mercado direcionados a web, a preocupação com performance cresceu e o resultado disso foi o recente release da versão 1.9.1 do ruby, com resultados impressionantes de performance.
Deixando a performance de lado e voltando à DSL, em um projeto Rails você pode fazer as seguintes associações em uma classe model:
has_many :favorites, :conditions => {:state => ‘public’}
has_many :roles, :through => :projects, :uniq => true
validates_length_of :login, :within => 3..40, on => :create
validates_presence_of :authority, :if => :in_leadership_role, :message => “must be authorized for leadership”
Fica claro e limpo que o modelo tem uma associação a duas coleções (favorites e roles), e validações ficam explícitas no próprio model. Outro exemplo usado na apresentação citada acima:
#Um intervalo de tempo:
3.years + 13.days + 2.hours
# Quatro meses de agora, na segunda_feira
4.months.from_now.next_week.monday
Muita coisa é possível fazer no ruby por causa do “method_missing” que existe nos objetos. Por exemplo, você pode sobrescrever esse método e incluir um código como esse:
def element(element_name, opts={})
write “<#{element_name}#{encode_opts(opts)}”
if block_given?
puts ”>#{yield}”
else
puts “/>”
end
end
E o resultado disso pode ser fazer uma manipulação simples de html:
xml.html {
xml.head{
xml.title(“History”)
}
xml.body{
xml.h1(“Header”)
xml.p(“paragraph”)
}
}
Outro exemplo que eu adoro é o uso de Active Record do Rails. Graças ao “method_missing” podemos fazer uso de métodos que não existem, como por exemplo:
Funcionario.find_by_nome_and_cargo( "Fulano", "Gerente")
E ainda fazer uso de Named Scopes:
Funcionario.gerente.pelo_nome
Na linha acima o Active Record vai trazer todos gerentes ordenados pelo nome.
Ruby é uma linguagem muito boa para escrever DSL internas por ser uma linguagem não-obstrusiva e permite que muitas pontuações sejam opcionais. Porém, DSL não faz o seu software magicamente melhor e devem ser usados com precauções. Nem sempre o código fica mais limpo com esse uso.
Glenn fala sobre a complexidade do software e a indicação do famoso livro de DDD do Eric Evans, e do livro The Mythical Man-Month.
Um bom design de software:
- Elimine tudo que possível da complexidade acidental
- Separe o resto
Outras frases muito interessantes dessa apresentação:
- Linguagens são para pessoas entenderem o domínio
- Coisas que são implícitas na verdade são complexidades acidentais
- Aprender a linguagem ajuda a entender o domínio
Um bom design de API:
- Criar uma DSL para determinada construção é poderoso
- Você pode refatorar ela quando achar duplicação, complexidade, etc
- DSL interna é apenas uma parte do bom design da API no Ruby