[Jifty-commit] r3131 - in jifty/trunk: .
jifty-commit at lists.jifty.org
jifty-commit at lists.jifty.org
Sun Apr 15 12:07:44 EDT 2007
Author: jesse
Date: Sun Apr 15 12:07:40 2007
New Revision: 3131
Added:
jifty/trunk/doc/talks/oscon.2007.dsls.txt
Modified:
jifty/trunk/ (props changed)
jifty/trunk/doc/talks/ (props changed)
Log:
r55445 at pinglin: jesse | 2007-04-15 12:06:01 -0400
* Start of the oscon 2007 talk
Added: jifty/trunk/doc/talks/oscon.2007.dsls.txt
==============================================================================
--- (empty file)
+++ jifty/trunk/doc/talks/oscon.2007.dsls.txt Sun Apr 15 12:07:40 2007
@@ -0,0 +1,798 @@
+#title Jesse Vincent - Best Practical
+Domain Specific Languages in Perl
+---
+#title A bit about DSLs
+DSLs are little languages for specific programming tasks
+---
+DSLs are easier to read
+---
+DSLs are more expressive
+---
+DSLs let you optimize your code for coding
+---
+Mostly, I'm going to talk about "Englishy" DSLs
+---
+Not all DSLs are Englishy
+---
+SQL
+---
+Regexes
+---
+Excel Macros
+---
+XML config files
+---
+XSL-T
+---
+GraphViz
+---
+...but I've been on an Englishy DSL kick
+---
+DSLs can be implemented in your 'host' language
+---
+(These get called "internal" DSLs)
+---
+DSLs can be implemented outside your 'host' langauge
+---
+(External DSLs)
+---
+Everything I'm going to talk about is Pure Perl (Internal)
+---
+The Ruby community is big on DSLs
+---
+You can make DSLs in Perl, too
+---
+(but it does take more work in Perl)
+---
+How did I get here?
+---
+#title How did I get here?
+Airplane
+---
+Narita Express
+---
+Subway
+---
+Ok, How'd I really get here?
+---
+All started at OSCON 2005
+---
+DHH demonstrated Rails migrations
+---
+#mode ruby
+class AddUserTable < ActiveRecord::Migration
+ def self.up
+ create_table :users do |table|
+ table.column :name, :string
+ table.column :login, :string
+ table.column :password, :string, :limit => 32
+ table.column :email, :string
+ end
+ end
+
+ def self.down
+ drop_table :users
+ end
+ end
+---
+They looked very sexy
+---
+I was very jealous
+---
+"You can't do this in any other language"
+---
+Never say that to a Perl Hacker
+---
+#title Agenda
+We've made some DSLs
+---
+One for declaring database schema
+---
+(I thought Rails did more. But I never read the manual)
+---
+(It does a lot more than Rails migrations)
+---
+One for web templating
+# loc-ja じゃあいったいどうして?!
+---
+(Yes, another web templating system)
+---
+(Hopefully, this one will provide some closure)
+---
+(You'll see)
+---
+One for making web testing easier
+---
+(It's VERY beta)
+---
+(Perfect for Web 2.0)
+---
+#title
+Jifty::DBI::Schema
+---
+#title Jifty::DBI::Schema - The design process
+Delarative Syntax for an Object Relational Mapper
+---
+Started sketching Jifty::DBI columns
+---
+Started with DBIx::SearchBuilder
+---
+Columns were defined as a hash
+---
+#mode perl
+sub _CoreAccessible {
+ { id => {
+ read => 1,
+ sql_type => 4,
+ length => 11,
+ is_blob => 0,
+ is_numeric => 1,
+ type => 'int(11)',
+ default => ''
+ },
+ Name => {
+ read => 1,
+ write => 1,
+ sql_type => 12,
+ length => 200,
+ is_blob => 0,
+ is_numeric => 0,
+ type => 'varchar(200)',
+ default => ''
+ },
+ }
+ ...
+}
+---
+Hashes are kind of ugly
+---
+We spent about a month playing with syntax.
+---
+Our first goal was "feels right"
+---
+Our second goal was "we can implement this"
+---
+I'm going to show you some of our design process
+---
+(It's a mix of code and IRC)
+---
+#mode perl
+$x = Jifty::DBI::SchemaBuilder->new;
+$x->define_blablalb
+$x->bla bla
+---
+#mode perl
+our db_table 'addresses';
+our field name => {
+ has_type 'varchar';
+ has_default 'frank'
+};
+---
+<chmrr> (by the way, i'm pretty sure we don't get to do the sub-at-the-end thing either... I tried lots of hacky ways to get it working and failed.)
+
+<glasser> yeah, I think we're going to end up having a pseudo-sub that's really a hash behind the scenes
+---
+#mode perl
+{
+ my $s = Jifty::DBI::SG->import_functions;
+
+ db_table bla bla bla;
+ field bla;
+ field bar;
+} # $s.DESTROY gets called and unimports db_table/field/...
+---
+(This was astonishingly close to what we do today.)
+---
+#mode perl
+my $schema = Jifty::DBI::RecordSchema->new;
+$schema->for_class(__PACKAGE__);
+
+$schema->field name => {
+ has_type 'varchar';
+ has_default 'Frank'
+}
+
+---
+#mode perl
+
+BEGIN { @ISA = 'Jifty::DBI::Record' }
+use Jifty::DBI::Record; # but this sucks!
+
+use base qw/Jifty::DBI::Record/;
+
+__PACKAGE__->schema_version (0.0001)
+# (or some other method that does two thing evily).
+---
+<obra> we could tie @ISA
+
+<obra> ...I'm kidding
+---
+<obra> we could tie the symbol table
+
+<obra> ...It just doesn't work
+---
+#mode perl
+use base 'Jifty::DBI::Record';
+Jifty::DBI::Record->___from_code();
+
+db_table 'addresses';
+
+field {
+ called 'name'; # ?
+}
+---
+<glasser> is
+
+ "has_type 'string'"
+
+definitely better than
+
+ "type => 'string'"
+
+in your book?
+---
+<obra> how would you do:
+
+ refers_to_many RT::Tickets by 'owner';
+
+<Robrt> hmm. i thought about this before. we can do like simon and
+
+ refers_to_many "RT::Tickets by owner";
+
+<Robrt> but I don't really like that. parsing is lame.
+
+<Robrt> I'm *pretty* sure that we can't get the line you've written to compile.
+---
+<obra> I've got a bad perl5 idea for you. Robert claims it's impossible
+
+<obra> I'm trying to make the syntax "refers_to_many 'BTDT::Model::Tasks' by 'owner';" valid perl5 syntax.
+---
+<audreyt> well, that may be true but you don't want that.
+
+<audreyt> refers_to_many BTDT::Model::Tasks by 'owner'
+
+<audreyt> is more readable and easily implemented.
+
+<audreyt> sub by ($) { by => @_ }
+
+<audreyt> done!
+
+<audreyt> stop thinking classes as strings :)
+---
+<obra> shit! it actually works!
+---
+What we had left:
+---
+the "field foo => sub {};" issue
+---
+#mode perl
+
+ # We wanted something that acted like this
+ # But without the ugly 'sub' keyword
+ field email => sub {
+
+ has_type 'varchar';
+ has_default 'Frank';
+};
+
+---
+#mode perl
+# We cculd do this, but it used a hash
+# not a block
+
+field phone => {
+ has_type 'varchar';
+};
+
+field employee_id => {
+ refers_to_a Sample::Employee;
+}
+---
+#mode perl
+
+# This is ugly and verbose
+
+package Sample::Employee;
+
+use base qw/Jifty::DBI::Record/;
+
+__PACKAGE__->db_table 'employees';
+
+__PACKAGE__->field name => has_type 'varchar';
+
+__PACKAGE__->field dexterity => { has_type 'integer'};
+
+---
+#title In the end...
+We ended up with Jifty::DBI columns
+---
+#mode perl
+use Jifty::DBI::Record schema {
+column auth_token => type is 'text',
+ render as 'Unrendered';
+
+column score => type is 'int',
+ is immutable,
+ label is 'Score';
+
+column time_zone =>
+ label is 'Time zone',
+ since '0.0.12',
+ default is 'America/New_York',
+ valid are formatted_timezones();
+};
+---
+We implemented the DSL twice
+---
+#title
+Take 1
+---
+#title Take 1
+Jifty::DBI::Schema
+---
+#title Take 1: Jifty::DBI::Schema
+Our first DSL in Perl
+---
+We beat the parser into submission with a few tricks
+---
+Injection of functions
+---
+We saw that a moment ago
+---
+Clever function prototypes
+---
+Let's have a look at that
+---
+#mode perl
+# The syntax we wanted
+
+score => type is 'int',
+ is immutable,
+ default is '0',
+ render as 'text',
+ label is 'Score',
+ since is '0.0.7';
+---
+#mode perl
+# perl -MO=Deparse parses that as:
+
+'is'->type('int',
+ 'immutable'->is,
+ 'is'->default('0',
+ 'as'->render('text',
+ 'is'->label('Score',
+ 'is'->since('0.0.7')))));
+---
+How can we fix that?
+---
+Prototype hacking!
+---
+#mode perl
+sub is ($) { return shift };
+sub as ($) { return shift };
+sub since ($) { }
+sub type ($) { }
+sub render ($) {}
+sub label ($) {}
+sub default ($) {}
+---
+#mode perl
+
+# Now, perl -MO=Deparse parses that as:
+
+ type(is('int')),
+ is('immutable'),
+ default(is('0')),
+ render(as('text')),
+ label(is('Score')),
+ since(is('0.0.7'));
+---
+Downsides
+---
+Limited flexibility
+---
+Needs new functions for every attribute
+---
+#title Take 2
+#`mpg123 ~/katamari.mp3`
+Object::Declare
+---
+#title Take 2: Object::Declare
+Katamari for Code
+---
+#mode perl
+use Jifty::DBI::Record schema {
+
+ column score => type is 'int',
+ is immutable,
+ render as 'text',
+ default is '0',
+ label is 'Score',
+ since is '0.0.7';
+};
+---
+# mode perl
+# perl -MO=Deparse parses that as:
+
+'is'->type('int',
+ 'immutable'->is,
+ 'is'->default('0',
+ 'as'->render('text',
+ 'is'->label('Score',
+ 'is'->since('0.0.7')))));
+---
+Why fight the parser?
+---
+Perl gives us all the rope we need
+---
+UNIVERSAL:: and ::AUTOLOAD
+---
+What actually happens at compile time:
+
+---
+The 'schema' function in our baseclass takes a code block
+---
+...and returns a closure
+---
+Jifty::DBI::Record::import takes over:
+---
+it takes the closure
+---
+it installs some methods...
+---
+...is::AUTOLOAD and UNIVERSAL::is and as::AUTOLOAD
+---
+it runs the closure
+---
+it hands the result off to a method of your choice
+---
+it removes its magic symbols
+---
+...and then your program gets control back
+---
+#title Jifty::DBI::Schema - end
+That's Jifty::DBI::Schema.
+---
+#title
+Template::Declare
+---
+#title Template::Declare
+A pure Perl Templating Language
+---
+What it looks like
+---
+#mode perl
+template '/pages/mypage.html' => sub {
+ html {
+ head {};
+ body {
+ h1 {'Hey, this is text'};
+ }
+ }
+};
+---
+#mode perl
+template choices => page {
+ h1 { 'My Choices' }
+ dl {
+ my $choices = Doxory::Model::ChoiceCollection->new;
+ $choices->limit(
+ column => 'asked_by',
+ value => Jifty->web->current_user->id,
+ );
+ while (my $c = $choices->next) {
+ dt { $c->name, ' (', $c->asked_by->name, ')' }
+ dd { b { $c->a } em { 'vs' } b { $c->b } }
+ }
+ }
+};
+---
+But!
+---
+Content! Templates!
+---
+Design! Code!
+---
+OMGWTF!? THAT'S WRONG!
+---
+The person who told you it's wrong was lying to you.
+---
+We're Perl hackers
+---
+Why are we writing our templates in another language?
+---
+This is not 1997
+---
+It's 2007
+---
+People use CSS for design now
+---
+Programmers still have to make templates
+---
+Templates run like CODE
+---
+Because they ARE code
+---
+Let's use our CODING tools to work with them
+---
+#mode perl
+#title Refactoring
+
+template 'mypage.html' => page {
+ h1 { 'Two choices' };
+ div { attr { class => 'item' };
+ h2 { 'Item 1'};
+ };
+ div { attr { class => 'item' };
+ h2 { 'Item 2'};
+ };
+};
+---
+#title Refactoring
+#mode perl
+
+template 'mypage.html' => page {
+ h1 { 'Two choices' };
+ for ("Item 1", "Item 2") { item($_); }
+};
+
+sub item {
+ my $content = shift;
+ div {
+ attr { class => 'item' };
+ h2 {$content};
+ };
+
+}
+---
+We can refactor templates!
+---
+Have you ever tried to refactor HTML?
+---
+Our HTML is magically valid
+---
+(Syntax errors are...Syntax Errors)
+---
+#title Stashing our templates
+#mode perl
+template '/foo/index.html' => sub {... };
+---
+'sub template' takes a name and a coderef
+---
+But where do we put these?
+---
+We need a global stash
+---
+It needs to be per package
+(Don't want to mix things together)
+---
+Basically, we need a symbol table
+---
+It's Perl
+---
+We have THE symbol table
+---
+But URLs have characters that are illegal in sub names. :/
+---
+Actually, Perl doesn't care
+---
+#mode perl
+no strict 'refs';
+*{ $class . '::' . $subname } = $coderef;
+---
+That just works.
+---
+Even if your sub is named './\\foo#title <>'
+---
+But how do you call it?
+---
+#mode perl
+# perldoc UNIVERSAL
+---
+CLASS->can( METHOD )
+"can" checks if the object or class has a method called "METHOD".
+If it does then a reference to the sub is returned.
+---
+#title Closures
+Now, about that syntax.
+---
+HTML tags take blocks of content
+---
+Our tag methods take blocks of Perl
+---
+They return closures when you want them to
+---
+They run and output their content when you want them to
+---
+#mode perl
+sub h1 (&;$) {
+ my $code = shift;
+
+ ...
+
+ if (defined wantarray) {
+ return sub { ...closure around $code...};
+ } else {
+ # Actually do our work, run $code and return the output
+ return $code->();
+ }
+}
+---
+We install methods for every HTML tag
+---
+(Except 'tr'. Anybody know why?)
+---
+#mode perl
+use CGI ();
+install_tag($_)
+ for ( @CGI::EXPORT_TAGS{
+ qw/:html2 :html3 :html4
+ :netscape :form/}
+);
+---
+#title Not everything is roses
+(Here's where it all goes wrong)
+---
+HTML Attributes
+---
+# mode perl
+# What we've got:
+
+div { attr { id => 'my-div'};
+ ...
+};
+
+# and
+
+with ( id => 'my-div'), div {
+...
+};
+---
+# mode perl
+# What I think I'd like:
+
+div ( id => 'my-div' ), {
+...
+}
+---
+So, what's the big problem?
+---
+Just change the prototype
+---
+In Perl, the (&) in a prototype MUST come first
+---
+ORZ
+---
+Can anybody help me?
+---
+#title Template::Declare - end
+That's Template::Declare
+---
+#title
+Test::WWW::Declare
+---
+#title Test::WWW::Declare
+A language for testing web applications
+---
+In early development
+---
+It might change
+---
+Web test scripts are UGLY
+---
+Test::WWW::Declare is PRETTY
+---
+Simple, declarative web testing
+---
+Easy to read
+---
+Easy to write
+---
+Looks more like what users do
+---
+#mode perl
+# Test::WWW::Mechanize
+
+my $server=Jifty::Test->make_server;
+isa_ok($server, 'Jifty::Server');
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+$mech->get_html_ok($URL);
+like($mech->uri, qr{splash},
+ 'Redirected to splash page');
+---
+The insides are great
+---
+The syntax ain't
+---
+We built on Test::More and WWW::Mechanize
+---
+#mode perl
+
+# Test::WWW::Declare
+
+session "search" => run {
+ flow "google searches work" => check {
+ get 'http://google.com/ncr';
+ fill form 'f' => {
+ q => 'Squeamish ossifrage' };
+ click button 'Google Search';
+ }
+};
+---
+Regular tests keep running on failure
+---
+Makes no sense when a failure means you lose context
+---
+Every 'check' block aborts on failure
+---
+Abort means 'failing test'
+---
+Every 'session' has a cookie jar and WWW::Mechanize
+---
+#mode perl
+session "check logins" => run {
+ flow "basic connectivity" => check {
+ get 'http://fsck.com';
+ content should match qr{fsck.com};
+ click href qr{book};
+ content should match qr{RT Essentials}i;
+ };
+};
+---
+What's the weird syntax?
+---
+# mode perl
+content should match qr{RT Essentials}i;
+---
+#mode perl
+content should match qr{RT Essentials}i;
+
+# vs
+
+ok($req->content =~ /RT Essentials/i);
+---
+# mode perl
+# How do we make this valid perl?
+
+content should match qr{RT Essentials}i;
+---
+Prototypes
+---
+#mode perl
+sub match ($) {
+ return shift;
+}
+---
+#mode perl
+sub should ($) {
+ my $item = shift;
+ return $item;
+}
+---
+#mode perl
+sub content ($) {
+ my $regex = shift;
+ unless ( mech()->content =~ /$regex/ ) {
+ die "Content did not match $regex";
+ }
+}
+---
+#title Test::WWW::Declare - end
+That's Test::WWW::Declare
+---
+#title Conclusion
+Creating DSLs is lots of fun
+---
+Creating DSLs can be a lot of work
+---
+Creating DSLs helps you learn Perl internals
+---
+Creating DSLs helps find bugs in Perl
+---
+DSLs can make coding more fun
+---
+Challenge: CPAN some Japanese DSLs
+---
+Thanks
+---
More information about the Jifty-commit
mailing list