RailsConf: Steven Hammond - DSLs and Rails Plugins
Posted by Nick Sieger Sat, 24 Jun 2006 16:18:00 GMT
Intro
Steven talks on the whats and whys of domain specific languages.
Steven’s background is is as a java developer and more recently, a project manager, or “sith lord” as he put it.
Some early exposure to DSLs for him involved the use of something called Program Composition Notation that was used to describe a weather modeling program to be run on massively-parallel supercomputers.
Features of DSLs
- Usually a partial programming language focused on a single problem domain.
- Replicate syntax/structure that is common to users of that domain.
- Tries to be as elegant, concise and expressive as possible in that domain.
Horizontal vs. Vertical
- Horizontal -- programming problems (e.g., Rails migrations)
- Vertical -- business domain problems (e.g., a game DSL, insurance product definition, circuit board specification)
Recipe example
Steve contrasted a procedural, C-style recipe vs. a Ruby-based DSL that used blocks to delineate ingredients from procedures. The differentiating point was “which one could your mom translate into meatloaf”? I suppose neither if you don’t like your mom’s meatloaf.
Rails DSLs
- The framework itself (“is the whale shark a fish?”)
- Migrations
- RJS templates
- Plugins
acts_as_state_machine
- RESTful Rails
game_dsl
When to create a DSL
- established syntax or vertical domain
- soemthing that’s inelegant in normal imperative or object-oriented style
- abstract implementation details so that you don’t depend on them (e.g., migrations does this for database DDL)
- reuse
DSLs and Reuse
- “Reuse is vastly overrated” -- DHH in 1/2006
- Business logic is very application specific and is difficult to reuse across projects
- Level of reuse is at a lower lever where there is an information barrier to understanding the component (e.g., LINPACK)
Designing a DSL
- Be careful to balance forward-looking with extraction. In other words, DSLs should be extracted or driven by need just like anything else.
- Layout a domain syntax (not necessarily in Ruby)
- Extract and evolve over time
- Translate business requirements/statements into succinct programming fragments. (e.g., rolling dice and assigning attributes)
# extending Integer to deal with die-rolling
3.d6
3.d6 :keep_best => 3
# Deck-dealing example
class Tile < AciveRecord::Base
acts_as_deck
end
class Player < ActiveRecord::Base
acts_as_player
end
class DeckController < ApplicationController
def deal_cards
@deck = Tile.find :all
@deck.shuffle
@card = @deck.draw
@deck.deal :cards => 6, :to => @players
end
end
There was a discussion at this point about whether it was necessary to use a DSL in this case, and Obie Fernandez piped in that if you’re aiming for reuse or as an API, DSLs give a readable, understandable representation that makes reuse and sharing easier.
Another comment was regarding namespace collisions, and Steven replied honestly that there is not a good answer. If you combine a game-playing DSL with a home-building DSL, are you randomizing your porch?
YAML provides another good medium for succinct data layout (e.g., database.yml). Steven’s example followed the same pattern to apply to selecting different sources for a random number generator.
Pitfalls
- DSLs should not necessarily be the first solution -- the domain could be too narrow or too broad.
- Too many DSLs (or even plugin bloat) could end up compromising the readability of the code. Namespacing comes in here. Which DSL/plugin provided the given functionality?
- Interaction side-effects -- it’s not clear how mixing metaphors can make the solution clearer.
- Vertical DSLs additionally suffer from the problem that it’s still programming, and there are syntax issues to overcome, even though the DSL can faciliate interactions with the customer.
A comment came from Aslak Hellesoy about the use of external DSLs (i.e., built using parser generators). Steven replied that it is appropriate in some cases, but the complexity will be higher.
It seems to me that in the case of external DSLs, the freedom (lack of constraints) can actually be a burden. You can design syntax however you want but you need to be sure that it makes sense to the people who will be using it. Unless you’re really good with language and interface design it can be easy to create a custom, niche language that nobody knows anything about and is inflexible. In my experience it’s hard to use parser generators like yacc to build flexible parsers. Ruby turns out to be really good for DSLs because you can omit details (parentheses and hash curly braces) and it still does what you want.