Disclaimers:
- Catalyst chained dispatching is great
- Dave Rolsky is a fantastic, underpraised hacker, a great guy, and one of the first people to say that an interface or implementation could use improvement
- Martin Fowler is smart and a deep thinker
- I like MooseX::Declare and its ilk
- I don't object to what these systems do; I question how they do it
- If it makes sense for you—if you've thought about the benefits and drawbacks and the benefits outweigh the drawbacks and costs—use it with no quibbles or qualms on my part. I do sometimes.
Your embedded so-called "domain specific language" is only pretty if your language's liberation army has kidnapped and brainwashed you so long ago that your notion of beauty and concision is horribly warped.
To wit:
use CatalystX::Routes;
BEGIN { extends 'Catalyst::Controller'; }
# /user/:user_id
chain_point '_set_user'
=> chained '/'
=> path_part 'user'
=> capture_args 1
=> sub {
my $self = shift;
my $c = shift;
my $user_id = shift;
$c->stash()->{user} = ...;
};
... is not particularly pretty. It may make the intent of the declaration of routing clearer than the alternative:
sub _set_user :Chained('/') :PathPart('user') :CaptureArgs(1)
{
my $self = shift;
my $c = shift;
my $user_id = shift;
$c->stash()->{user} = ...;
}
... but suggesting that it's pretty goes a bit far. I don't have many
better syntaxes in mind, but if I could remove the inline =>
sub { ... }
and the need for fat arrows everywhere, I would. Something
like this might be more aesthetically pleasing:
chained: /
path_part: user
capture_args: 1
method _set_user($c, $user_id)
{
...
}
... although it might be fun to write:
method _set_user($c, $user_id)
as user, chained to /, capturing one arg
{
...
}
... if route declarations must be in the same file or attached to the same method as declarations. Then again, the Catalyst debugging syntax is awfully clear:
//*
Perl 5 is hardly the only offender in the syntax department though. Here's a so-called embedded DSL in Python 2:
with create_table("Employee") as t:
t.first_name = {"type" : "char", "length" : 30 }
t.last_name = {"type" : "char", "length" : 30 }
t.age = {"type" : "int"}
To whom is this nicer than SQL? (You'd almost think someone had already created a language specific to the domain of describing relational data structures.)
Somehow Ruby fans fail to notice the baffling need to sprinkle syntax all over their tooth-decaying sugar, as in Cucumber, where you get the illusion of writing test specifications in plain English as well as the absolute joy of writing and maintaining regular expressions to extract the necessary data from them.
That's right, the important data structure in your test suite is semi-structured text:
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press add
Then the result should be <output> on the screen
Examples:
| input_1 | input_2 | output |
| 20 | 30 | 50 |
| 2 | 5 | 7 |
| 0 | 40 | 40 |
... and you still have to parse it yourself, in an ad hoc fashion:
Given /I have entered (.*) into the calculator/ do |n|
calculator = Calculator.new
calculator.push(n.to_i)
end
There's a cleverness there to be sure, but is that really the best the programming world can provide?
Lisps fare little better, where outside of Dylan, every Lisp program ends up looking like every other Lisp program. At least XSLT had the good grace to reveal that homoiconicity means that ugly languages make for programs that are ugly to the bone.
Designing great languages is difficult. That's one reason why there are so many languages. If the best you can do as the writer of a so-called embedded dynamic language is to find some corner cases in the syntax of your host language then sprinkle regular expressions around, you haven't done much at all. (Tell me a non-programmer is always going to follow Cucumber's "But it's only semi-structured plain text, it's so eeeeeeeeeezy to write!" semi-structured format in a way that your ad hoc regular expressions always match perfectly, and I'll show you non-programmers who write their plain-text behaviors in Microsoft Word.)
Again, if these tools make your life easier, great. I use them sometimes myself. I just don't dislocate my shoulder patting myself on the back for my cleverness in doing so. Maybe some of the energy spent trying to twist a host language into not looking like the host language would be better spent writing simpler, more fluent interfaces in the dominant and effective idioms—if not writing actual new domain-specific languages, with parsers and everything.
I just hope that Dancer or Mojo won't fall into the same trap and become bloatware :p BTW, "BEGIN { extends ... }" is the fugliest part of the code
It's all relative. I was _so_ fed up with the horrible attribute stuff that I consider CatalystXD::Routes a real improvement. As I mentioned in my blog post, the fact that this is all actual Perl runtime code has a lot of value too, although mst later pointed out to me that there's already a way to declare actions without sub attrs (but it's even uglier than CX::Routes).
I agree that the examples you showed are much nicer, but CatalystX::Declare already exists.
Finally, CX::Routes integrates with Catalyst::Action::REST so as to make writing RESTful code easier and cleaner, which is one of the main reasons I wanted to write it in the first place.
@Burak: Putting extends in a BEGIN block is only necessary if you want to have sub attributes in your controller. I did that in the synopsis code to show that you can mix CX::Routes and sub attr actions in one controller.
I agree with all of your reasoning. I'm considering using your code.
Imagine how much nicer syntax we could have if we weren't tied to what we could convince the Perl 5 parser to accept though. I'm dreaming under a big blue sky here!