Your SlideShare is downloading. ×
0
Cleanliness is Next to
Domain-Specificity
Ben Scofield
Senior Developer
Viget Labs




4 November 2007
© Copyright 2007 Vi...
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




                                                       Intim...
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...
Linguistic Determinism
The Hopi
Linguistic Relativism
http://www.flickr.com/photos/maxhunter/79993854/




                                                   Snow
             ...
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....
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




                 ...
48
                                                                                      40




   The Old Way, cont.
def ...
40




                       Output
session_url = quot;/k/ident/apisession?token=#{token}quot;



search_url = quot;/s/ap...
40




              Expectations
class KayakTest < Test::Unit::TestCase
  def test_find_should_call_out_to_session_endpoi...
40




   Parsing Responses
class KayakTest < Test::Unit::TestCase
  def test_session_response_should_be_parsed_for_sessio...
class Kayak




                                      First Cut
  @@session_id = nil
  @@search_id = nil
  @@search_option...
module Kayak




                          Second Cut
  TOKEN    = '12345'
  HOSTNAME = 'www.kayak.com'
  PORT     = 80

 ...
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 argume...
*
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
 © Copyrigh...
Upcoming SlideShare
Loading in...5
×

Cleanliness is Next to Domain-Specificity

1,537

Published on

Presentation from Rubyconf 2007 - on linguistics and Ruby DSLs

Published in: Technology, Design
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,537
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
33
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Transcript of "Cleanliness is Next to Domain-Specificity"

  1. 1. Cleanliness is Next to Domain-Specificity Ben Scofield Senior Developer Viget Labs 4 November 2007 © Copyright 2007 Viget Labs, LLC – www.viget.com
  2. 2. Part 1: Linguistics Part 2: Refactoring
  3. 3. The Ruby Community
  4. 4. http://www.flickr.com/photos/jesper/1395418767/ Interdisciplinary
  5. 5. Linguistics
  6. 6. Categories
  7. 7. Regional Dialects
  8. 8. (more) Regional Dialects
  9. 9. Jargons Cants
  10. 10. Pidgins and Creoles
  11. 11. Vocabulary Grammar
  12. 12. Real DSLs Ruby Domain-Specific Code
  13. 13. ActiveRecord
  14. 14. RSpec
  15. 15. Same Grammar, Different Vocabulary
  16. 16. Who Cares?
  17. 17. DSLs DSL Intimidate and Frighten http://www.flickr.com/photos/cwsteeds/58514985/
  18. 18. Write a Parser? No, Thanks. http://www.flickr.com/photos/rooreynolds/243810988/
  19. 19. http://www.flickr.com/photos/jonosd/498162310/
  20. 20. Change the Vocabulary Change the World Heroes on NBC - Mondays at 9 PM
  21. 21. API vs. Dialect
  22. 22. Why DSanything?
  23. 23. Who Are We?
  24. 24. 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
  25. 25. Linguistic Determinism
  26. 26. The Hopi
  27. 27. Linguistic Relativism
  28. 28. 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 ... ...
  29. 29. Color Perception http://www.flickr.com/photos/thedeplorableword/140856437/
  30. 30. Direction of Causality Degree of Influence
  31. 31. RSpec  Sapir-Whorf
  32. 32. Testing is Too Late
  33. 33. Specifications Come First
  34. 34. RSpec Leads You in the Right Direction
  35. 35. DSDs are Built on Linguistic Relativism
  36. 36. Keep Your Head in the Domain
  37. 37. Refactoring
  38. 38. Tastes Vary
  39. 39. ?
  40. 40. Finding a Ticket
  41. 41. kayak.com
  42. 42. What Does This Do?
  43. 43. Ruby? Awesome!
  44. 44. 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.
  45. 45. Start at the End
  46. 46. I Want to: find flights from CLT to RDU leaving today and returning in one week
  47. 47. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  48. 48. 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
  49. 49. 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
  50. 50. 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;
  51. 51. 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
  52. 52. 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
  53. 53. 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
  54. 54. 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 ...
  55. 55. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  56. 56. Third Cut ?
  57. 57. Always Room for Improvement
  58. 58. Tips
  59. 59. :symbol ignore the colon
  60. 60. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  61. 61. Optional Parentheses looks like a sentence
  62. 62. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  63. 63. 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
  64. 64. * arrays from anything
  65. 65. Optional Braces not that common
  66. 66. I Want to: find :flights, :from => :CLT, :to => :RDU, :leaving => Date.today, :returning => Date.today + 7
  67. 67. • Start Modestly • Stay in the Domain • Get Better
  68. 68. 56 That’s It travel safely
  69. 69. 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
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×