Domain Specific Languages

Molding Ruby

Joe O’Brien, artisan EdgeCase, LLC

image courtesy CafePress

goals
Why you should look at Ruby What else you can do with Ruby Why I can’t shutup about Ruby

image courtesy Fillmore Photography on flickr

.NET / Java Background EIA / SOA ThoughtWorks Columbus Ruby Brigade EdgeCase

I <3 emacs

“A language that doesn't affect the way you think about programming, is not worth knowing. “
Alan J. Perlis, Yale University

image courtesy Mark Round (lokidude99) on flickr

Ruby C Assembler

Ruby JVM CLR (DLR) C Assembler Rubinius

image courtesy Mark Round (lokidude99) on flickr

reading > writing

scattered, smothered, covered

Quad venti no whip 2 pump white mocha

Domain Specific Languages

I <3 emacs

Martin Fowler
martinfowler.com/dslwip

image courtesy Dave Thomas (pragdave) on flickr

“(DSL) is a computer language that's targeted to a particular kind of problem, rather than a general purpose language that's aimed at any kind of software problem.” Martin Fowler

not a new concept

mini-languages

external vs. internal

lex & yacc ANTLR

“[Lisp Programmers] follow a principle which could be called bottom-up design-changing the language to suit the problem.” Paul Graham

image courtesy niallkennedy on flickr

“In Lisp you don’t just write your program down toward the language, you also build the language up toward your program ... Language and program evolve together. Like borders between two warring states, the boundary between language and program is drawn and redrawn, until eventually it comes to rest along the mountains and rivers, the natural frontiers of your problem. In the end your program will look as if the language had been designed for it. And when language and program fit one another well, you end up with code which is clear, small and efficient.” Paul Graham

so why aren’t we using Lisp?

Ruby _not_ Rails

“... trying to make Ruby natural, not simple.” Yukihiro Matsumoto “Matz”

image courtesy Jim Lindley on flickr

“Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.”
from Revised Report on Scheme, 1991

ingredients
SmallTalk Lisp / Scheme Python Perl

interpreted

pure object-oriented language
>> => >> => >> => >> => 9.succ 10 9.nil? false 9.between? 8, 10 true 8 < 9 true

flexible syntax
class Dog def bark puts “woof” end end

open classes
class Integer def pm puts "#{self} pm" end def am puts "#{self} am" end end 9.pm 10.am

are you kidding?

Smalltalk influence

Lisp: code == data

implicit self

api vs. dsl

I <3 emacs

context

class Employees < ActiveRecord::Base belongs_to :organization validates_presence_of :name end

a few patterns have emerged

fluent interface / expression builder

Global Scoping

Declarative Programing / Object Scoping

named parameters

spill-over context

combinations
today.at 3.pm 3.days.from_today at(3.pm)

combinations

A Tale of 3 DSL’s

ACT I “oops I created a DSL”

[objo-laptop | ~ ] $ cat /usr/lib/php/build/Makefile.global mkinstalldirs = $(top_srcdir)/build/shtool mkdir -p INSTALL = $(top_srcdir)/build/shtool install -c INSTALL_DATA = $(INSTALL) -m 644 DEFS = -DPHP_ATOM_INC -I$(top_builddir)/include -I$(top_builddir)/main -I$(top_srcdir) COMMON_FLAGS = $(DEFS) $(INCLUDES) $(EXTRA_INCLUDES) $(CPPFLAGS) $ (PHP_FRAMEWORKPATH) all: $(all_targets) @echo @echo "Build complete." @echo "Don't forget to run 'make test'." @echo build-modules: $(PHP_MODULES) libphp$(PHP_MAJOR_VERSION).la: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(LIBTOOL) --mode=link $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -rpath $(phptempdir) $ (EXTRA_LDFLAGS) $(LDFLAGS) $(PHP_RPATHS) $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $ (EXTRA_LIBS) $(ZEND_EXTRA_LIBS) -o $@ -@$(LIBTOOL) --silent --mode=install cp $@ $(phptempdir)/$@ >/dev/null 2>&1 libs/libphp$(PHP_MAJOR_VERSION).bundle: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(CC) $(MH_BUNDLE_FLAGS) $(CFLAGS_CLEAN) $(EXTRA_CFLAGS) $(LDFLAGS) $ (EXTRA_LDFLAGS) $(PHP_GLOBAL_OBJS:.lo=.o) $(PHP_SAPI_OBJS:.lo=.o) $(PHP_FRAMEWORKS) $ (EXTRA_LIBS) $(ZEND_EXTRA_LIBS) -o $@ && cp $@ libs/libphp$(PHP_MAJOR_VERSION).so

image courtesy Jim Weirich http://onestepback.org

task :build do java_compile war_files end

What about dependencies?

task :build => [:clean, :test] do java_compile war_files end

task :build => [:clean, :test] do java_compile war_files end

a compromise

task :build => [:clean, :test] do |t| t.java_compile t.war_files end

desc “Builds the project, w00t!” task :build => [:clean, :test] do |t| t.java_compile t.war_files end

namespace “objo” do desc “Builds the project, ... task :build => [:clean, .... t.java_compile t.war_files end end

ACT II Consistency

class Resident has_many :entitlements end

def test_resident_should_have_many_entitlements association = Resident. reflect_on_association(:entitlements) assert_not_nil association, "Could not find an association for entitlements" assert_equal :has_many, association.macro assert association.klass. column_names.include? (association.primary_key_name), 'Expected the association to have’ + ‘a column referencing the class' end

def assert_model_has_many(model, association_name) association = Resident. reflect_on_association(association_name.to_sym) assert_not_nil association, "Could not find an association for #{association_name}" assert_equal :has_many, association.macro assert association.klass. column_names.include? (association.primary_key_name) end

class ResidentTest < ActiveSupport::TestCase def test_resident_should_have_many_entitlements assert_model_has_many Resident, :entitlements end end

class Resident < ActiveRecord::Base has_many :entitlements end

class ResidentTest < ActiveSupport::TestCase def test_resident_should_have_many_entitlements assert_model_has_many Resident, :entitlements end end

“If I can write it in one line, I want to test it in one line”
Stuart Halloway, Relevance LLC thinkrelevance.com

class Resident < ActiveRecord::Base has_many :entitlements end

class ResidentTest < ActiveSupport::TestCase test_should_have_many :entitlements end

class Test::Unit::TestCase class << self # ... def test_has_many(association_name) evaluate create_association_test(association_name, :has_many) end # .... private def create_association_test(association_name, association_type) class_under_test = self.to_s.gsub('Test', '').constantize meth = "def test_should_have_association_#{assoc.... meth << " model_name = #{class_under_test}\n" meth << " association = model_name.reflect_on_association.... meth << " assert_not_nil association, \"Could not find an ... meth << " assert_equal :#{association_type}, association.mac... meth << add_custom_checks(association_type, class_under_test) meth << "end" meth end def evaluate(string) puts "\n\n#{string}\n\n" if ENV['DEBUG'] class_eval string, __FILE__, __LINE__ end end

ACT III Business Language

MORN

Deliver every morning

CLOSE Deliver at closing time EVEN ONCE TWIC ASN Deliver anytime in the evening Deliver once a day Deliver twice a day Deliver as needed

# Morning MORN = PerHourRange.new(1, 0, 12) # Quitting time CLOSE = PerHourRange.new(1, 18, 24) # Evening EVEN = PerHourRange.new(1, 12, 24) # ....

XOD XAM XHS AP AB

= EverySoManyDays.new(2) = AM = EVEN & OD = (MORN | EVEN) & MinimumElapsedTime.new(6.hours) = (MORN | EVEN) & MinimumElapsedTime.new(6.hours)

se = ScheduleExpression::QMORN assert assert assert assert assert assert assert assert ! ! ! ! !

se.accept?(today_at(8), history(yesterday_at(12)) se.accept?(today_at(12), history(yesterday_at(12) se.accept?(today_at(8), history(yesterday_at(23,

se.accept?(today_at(8), history(yesterday_at(12), se.accept?(today_at(8), history(yesterday_at(12), se.accept?(today_at(12), history(yesterday_at(12) se.accept?(today_at(12, 1), history(yesterday_ se.accept?(today_at(16), history(yesterday_at(

QMORN.should_accept do delivered_at 3.pm previously_delivered_at do yesterday_at 3.pm today_at 4.pm end end

additional resources
• Rich Kilmer
http://rubyurl.com/8i89

• Martin Fowler http://martinfowler.com/dslwip • Pragmatic Programmers DSL’s in Ruby

additional resources

credits
• • • • • •
Jim Weirich - http://onestepback.org Paul Graham - http://paulgraham.com/progbot.html Martin Fowler -

• •

http://martinfowler.com/bliki/DomainSpecificLanguage.html http://martinfowler.com/dslwip

Jeremy Stell-Smith (JSS) http://www.onemanswalk.com Zak Tamsen (nakedz) Neal Ford http://nealford.com

credits
• •
Eric Raymond - The Art of Unix Programming Flickr: - http://flickr.com

• • • •

Mark Round (lokidude99) Dave Thomas (pragdave) Niall Kennedy (niallkennedy) Jim Lindley (strangecontext)

What I hear, I forget. What I see, I remember. What I do, I understand.
- Kung Fu Tzu (Confucius)

Twitter: objo objo.com theedgecase.com

Sign up to vote on this title
UsefulNot useful