Cleanliness is Next to Domain-Specificity
Upcoming SlideShare
Loading in...5
×
 

Cleanliness is Next to Domain-Specificity

on

  • 2,404 views

Presentation from Rubyconf 2007 - on linguistics and Ruby DSLs

Presentation from Rubyconf 2007 - on linguistics and Ruby DSLs

Statistics

Views

Total Views
2,404
Views on SlideShare
2,401
Embed Views
3

Actions

Likes
1
Downloads
32
Comments
0

2 Embeds 3

http://www.linkedin.com 2
http://www.slideshare.net 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Cleanliness is Next to Domain-Specificity Cleanliness is Next to Domain-Specificity Presentation Transcript

  • Cleanliness is Next to Domain-Specificity Ben Scofield Senior Developer Viget Labs 4 November 2007 © Copyright 2007 Viget Labs, LLC – www.viget.com
  • Part 1: Linguistics Part 2: Refactoring
  • The Ruby Community
  • http://www.flickr.com/photos/jesper/1395418767/ Interdisciplinary
  • Linguistics
  • Categories
  • Regional Dialects
  • (more) Regional Dialects
  • Jargons Cants
  • Pidgins and Creoles
  • Vocabulary Grammar
  • Real DSLs Ruby Domain-Specific Code
  • ActiveRecord
  • RSpec
  • Same Grammar, Different Vocabulary
  • Who Cares?
  • DSLs DSL Intimidate and Frighten http://www.flickr.com/photos/cwsteeds/58514985/
  • Write a Parser? No, Thanks. http://www.flickr.com/photos/rooreynolds/243810988/
  • http://www.flickr.com/photos/jonosd/498162310/
  • Change the Vocabulary Change the World Heroes on NBC - Mondays at 9 PM
  • API vs. Dialect
  • Why DSanything?
  • Who Are We?
  • http://www.oreillynet.com/onlamp/blog/2006/05/ sapirwhorf_is_not_a_klingon.html http://tech.puredanger.com/2006/11/08/does-your-programming- language-affect-how-you-think/ http://snakesgemscoffee.blogspot.com/2006_11_01_archive.html http://talklikeaduck.denhaven2.com/articles/2007/06/11/sapir- whorf http://adams.id.au/blog/2007/10/what-is-behaviour-driven- development/ http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/75914 http://www.weiqigao.com/blog/2007/09/10/ an_interesting_experiment_sapir_whorf_hypothesis.html http://www.ibm.com/developerworks/blogs/page/pmuellr? tag=ruby http://intertwingly.net/blog/2007/10/05/NOC http://brooders.net/category/perl/ http://gilesbowkett.blogspot.com/2007/02/sapir-worf-in- action_19.html http://blogs.msdn.com/daveremy/archive/2005/04/06/ sapirwhorfs.aspx http://erlangish.blogspot.com/2007/05/shape-of-your-mind.html http://www.oreillynet.com/onlamp/blog/2006/06/ how_does_a_programming_languag.html
  • Linguistic Determinism
  • The Hopi
  • Linguistic Relativism
  • http://www.flickr.com/photos/maxhunter/79993854/ Snow qanuk avalanche kaneq blizzard kanevvluk dusting natquik flurry nevluk frost aniu hail qanikcaq hardpack muruaneq igloo nutaryuk pingo qanisqineq powder qengaruk sleet utvak slush navcaq snow pirta snowflake pirtuk snowstorm ... ...
  • Color Perception http://www.flickr.com/photos/thedeplorableword/140856437/
  • Direction of Causality Degree of Influence
  • RSpec  Sapir-Whorf
  • Testing is Too Late
  • Specifications Come First
  • RSpec Leads You in the Right Direction
  • DSDs are Built on Linguistic Relativism
  • Keep Your Head in the Domain
  • Refactoring
  • Tastes Vary
  • ?
  • Finding a Ticket
  • kayak.com
  • What Does This Do?
  • Ruby? Awesome!
  • exit 0 @@results.each do |r| if searchtype == 'h' puts quot;#{r.price} url=#{r.url}quot; puts quot;#{r.stars} #{r.name} $#{r.loprice} - $#{r.hiprice}quot; elsif searchtype == 'f' puts quot;#{r.price} url=#{r.url}quot; r.legs.each do |leg| puts quot; #{leg}quot; end end end exit(0) more = poll_results_file(searchtype) @@results.each do |r| puts quot;#{r.price} #{r.url}quot; r.legs.each do |leg| puts quot; #{leg}quot; end end end Whoa.
  • Start at the End
  • I Want to: find flights from CLT to RDU leaving today and returning in one week
  • I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  • 48 40 The Old Way sid = getsession(@@token) searchid = start_flight_search(sid, ‘n’, ‘CLT’, ‘RDU’, Date.today, nil, 1) more = poll_results('f', sid, searchid, nil) while more == 'true' do more = poll_results('f', sid, searchid, nil) sleep(3) end def poll_results(searchtype, sid, searchid, count) url = quot;/s#{@@sparkleinstance}/apibasic/flight?searchid=#{searchid}&apimode=1&_sid_=#{sid}quot; more = nil Net::HTTP.start(@@hostname, @@port) do |http| if count url += quot;&c=#{count}quot; end response = http.get(url) body = response.body File.open(quot;ksearchbody.xmlquot;, quot;wquot;) do |f| f.puts(body) end more = handle_results(searchtype, body) if more != 'true' # save the body, so we can test without doin # an actual search File.open(quot;ksearchresults.xmlquot;, quot;wquot;) do |f| f.puts(body) end end end return more end
  • 48 40 The Old Way, cont. def handle_results(searchtype, body) xml = REXML::Document.new(body) more = xml.elements['/searchresult/morepending'] @@lastcount = xml.elements['/searchresult/count'].text @@sparkleinstance = xml.elements['/searchresult/searchinstance'].text if more more = more.text end if more != 'true' @@results = [] #puts quot;count=#{@@lastcount}quot; xml.elements.each(quot;/searchresult/trips/tripquot;) do |e| trip = Trip.new() e.each_element(quot;pricequot;) do |t| trip.price = t.text trip.url = t.attribute(quot;urlquot;) end e.each_element(quot;legsquot;) do |legs| legs.each_element(quot;legquot;) do |l| leg = Leg.new l.each_element do |ld| # extract the detail from each leg case when ld.name == 'airline': leg.airlinecode = ld.text #... end end trip.legs << leg end # leg in legs loop end # legs in trip loop #e.each_element(quot;/searchresult/trips/trip/pricequot;) { |p| trip.price = p.text } #puts quot;trip: #{trip.price}quot; @@results << trip end # each trip end return more end
  • 40 Output session_url = quot;/k/ident/apisession?token=#{token}quot; search_url = quot;/s/apisearch?basicmode=true&oneway=n&origin=#{origin} &destination=#{destination}&destcode=&depart_date=#{dep_date} &depart_time=a&return_date=#{ret_date}&return_time=a&travelers=# {travelers}&cabin=e&action=doflights&apimode=1&_sid_=#{sid}quot; results_url = quot;/s#{@@sparkleinstance}/apibasic/flight?searchid=# {searchid}&apimode=1&_sid_=#{sid}quot;
  • 40 Expectations class KayakTest < Test::Unit::TestCase def test_find_should_call_out_to_session_endpoint setup_mocks_for_find Kayak.find :flights end private def setup_mocks_for_find response = mock(:body => '<?xml version=quot;1.0quot;?> <ident> <uid>uid</uid> <sid>0123456789</sid> <token>12345</token> <error></error> </ident>') success = mock() success.expects(:get).with('/k/ident/apisession?token=12345').returns (response) Net::HTTP.expects(:start).at_least_once.yields(success) end end
  • 40 Parsing Responses class KayakTest < Test::Unit::TestCase def test_session_response_should_be_parsed_for_session_id setup_mocks_for_find Kayak.find :flights assert_equal '0123456789', Kayak.session_id end private def setup_mocks_for_find response = mock(:body => '<?xml version=quot;1.0quot;?> <ident> <uid>uid</uid> <sid>0123456789</sid> <token>12345</token> <error></error> </ident>') success = mock() success.expects(:get).with('/k/ident/apisession?token=12345').returns (response) Net::HTTP.expects(:start).at_least_once.yields(success) end end
  • class Kayak First Cut @@session_id = nil @@search_id = nil @@search_options = {} TOKEN = '12345' HOSTNAME = 'www.kayak.com' PORT = 80 class << self def session_id @@session_id end def method_missing(name, *args) @@search_options[name] end def find(type, conditions = {}) session_id ||= initialize_session @@search_options[:origin] = conditions[:from] @@search_options[:destination] = conditions[:to] @@search_options[:depart_date] = conditions[:leaving].strftime('%m/%d/%Y') if conditions[:leaving] @@search_options[:leave_date] = conditions[:returning].strftime('%m/%d/%Y') if conditions[:returning] search_id ||= initialize_search self end def initialize_search Net::HTTP.start(HOSTNAME, PORT) do |http| response = http.get(quot;/s/apisearch?basicmode=true&oneway=n&destcode=&depart_time=a&... if body = response.body xml = REXML::Document.new(body) @@search_id = xml.elements['//searchid'].text end end end def initialize_session Net::HTTP.start(HOSTNAME, PORT) do |http| response = http.get(quot;/k/ident/apisession?token=#{TOKEN}quot;) if body = response.body xml = REXML::Document.new(body) @@session_id = xml.elements['//sid'].text end end end end end
  • module Kayak Second Cut TOKEN = '12345' HOSTNAME = 'www.kayak.com' PORT = 80 class Flight attr_accessor :session, :search_id, :search_options def method_missing(name, *args) search_options[name] end def initialize(conditions = {}) session ||= Kayak::Session.new self.search_options = {} self.search_options[:origin] = conditions[:from] self.search_options[:destination] = conditions[:to] self.search_options[:depart_date] = conditions[:leaving].strftime('%m/%d/%Y') if conditions[:leaving] self.search_options[:return_date] = conditions[:returning].strftime('%m/%d/%Y') if conditions[:returning] Net::HTTP.start(HOSTNAME, PORT) do |http| response = http.get(quot;/s/apisearch?basicmode=true&oneway=n&destcode=&depart_time=a&return_date=... self.search_id = Kayak.retrieve(response, '//searchid') end end def self.find(args) self.new(args) end end class Session attr_accessor :session_id def initialize Net::HTTP.start(Kayak::HOSTNAME, Kayak::PORT) do |http| response = http.get(quot;/k/ident/apisession?token=#{Kayak::TOKEN}quot;) self.session_id ||= Kayak.retrieve(response, '//sid') end end end def self.find(type, *args) case type when :flights Kayak::Flight.find(*args) end end ...
  • I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  • Third Cut ?
  • Always Room for Improvement
  • Tips
  • :symbol ignore the colon
  • I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  • Optional Parentheses looks like a sentence
  • I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  • Blocks for All In other languages, you have to specify explicitly that a function can accept another function as an argument. But in Ruby, any method can be called with a block as an implicit argument. Matz, 2003
  • * arrays from anything
  • Optional Braces not that common
  • I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  • • Start Modestly • Stay in the Domain • Get Better
  • 56 That’s It travel safely
  • Ben Scofield ben.scofield@viget.com http://www.extendviget.com/ http://www.culann.com/ 4 November 2007 © Copyright 2007 Viget Labs, LLC – www.viget.com