Advanced Ruby - Day 3

20090311_avancedruby.jpg

The first two days of the training were really excellent. Great pace, clear explanations and samples, very technical, Chad and Dave really rock at Ruby.

Making Domain-Specific Languages

We start with a discussion on DSL versus an API.

Meat-and-Potato DSLs

  • Block based API.
  • Directly manipulating domain model classes
  • Defining classes and methods
  • Manually typing repetitive, low-level code

Example is Rake. Rake is a dependency oriented programming language.

Eexample from Dhaka (a paraser generator)

for_pattern("\n") do
 create_token("newline")
end

method_missing Trick

require 'pp'

class CommandListener
  def initialize
    @commands = []
  end
    
  def method_missing(*args)
    @commands << args
    self
  end
end

listener = CommandListener.new

listener.instance_eval do
  this object accepts whatever you type and stores it
end
pp listener

outputs:

#<CommandListener:0x80264
 @commands=
  [[:you, CommandListener],
   [:whatever, #<CommandListener:0x80264 ...>],
   [:accepts, #<CommandListener:0x80264 ...>],
   [:object, #<CommandListener:0x80264 ...>],
   [:this, #<CommandListener:0x80264 ...>],
   [:it],
   [:stores, #<CommandListener:0x80264 ...>]]>
listener = CommandListener.new
listener.instance_eval do
  this.object.accepts.whatever.commands.and.stores.it
end  

pp listener

outputs:

#<CommandListener:0x712b4
 @commands=
  [[:this],
   [:object],
   [:accepts],
   [:whatever],
   [:commands],
   [:and],
   [:stores],
   [:it]]>

Note any keyword can be used with method_missing…E.g. freeze is not a missing method.

Now by using a BlankSlate object we can avoid this issue:

class BlankSlate
  instance_methods.each do |method|
    undef_method(method) unless method =~ /^__/ || method == 'instance_eval'
   end
end

We can now create a command listener that can use any commands:

require 'pp'

class CommandListener < BlankSlate
  def initialize
    @commands = []
  end

  def method_missing(*args)
    @commands << args unless [:inspect, :to_s].include? args.first 
    self
  end
  
  def pretty_print(pp)
    pp.output.write @commands.inspect
  end
    
end

listener = CommandListener.new

listener.instance_eval do
  You can even type freeze!
end
pp listener

And we get the following output:

[[:freeze!], [:type, #<CommandListener:0x7ee28>], [:even, #<CommandListener:0x7ee28>], [:can, #<CommandListener:0x7ee28>], [:You, #<CommandListener:0x7ee28>]]

On the same principals we just create a morse encoder, and of course Dave had to show off and interface with the speech function of OSX and another version that interfaces with the MIDI controller.

Exotic Control Flow

Creating a loop with continuations

def start_loop
  callcc{|c|c }
end

def end_loop(c)
  c.call(c)
end


i = 0
again = start_loop

  puts i
  i += 1

end_loop(again) unless i > 5

Moving on…

We voted on what to cover next as we won’t have time to cover every thing…and the winners are:

* Concurrency                  ..................
* Debugging/Profiling          ................
* JRuby                        ......
* Ruby Extras                  ...........
* Distributed Programming      ......................

Distributed Programming

DRb

Marshaling

>> h = {:x => 1}
>> Marshal.dump(h)
=> “\004\b{\006:\006xi\006”
>> Marshal.load(_)
=> {:x=>1}

DRB Server

require 'drb/drb'
require 'ostruct'

DRb.start_service("druby://localhost:4321", OpenStruct.new)
DRb.thread.join

DRb Client

require ‘drb’

DRB.start_service
o = DRbOject.new_with_url("druby:''localhost:4321")
o.last_accessed = Time.now
o.some_other_arbitrary_method = "Set this on the open struct"

By default Pass-by-value, but can be Pass-by-reference can be enabled by including DRbUndumped.

  • Rinda
  • Ring

That’s all Folk! …my hands/brain where running tired during the last hour :-)

That was an incredible 3 days thanks to Chad and Dave. So if you want to dive deeper in Ruby, the Advanced Ruby is the best way to get there!

Enjoy!
Daniel

Posted by Daniel Wanja Wed, 11 Mar 2009 22:23:53 GMT


Comments

  1. Little Fyr about 3 hours later:

    What if you wanted to create a DSL that does NOT look like Ruby?

    For example, lets say I wanted my DSL to be some XML format?

    Why would I want to do that? Well, maybe I want to use Ant’s syntax for my new build system. This way I can leverage my existing ANT tools easily.

    Or maybe I want HTML to be a program, rather than static content. Why? Well maybe I want to build some content negotiation into some of the tags to simplify markup for some users (say sending inline SVG for some or a static image for others). Or maybe I want to automatically add “even” “odd” “first” and “last” class names to my li, tr, td and th elements. Or maybe I want to decorate any <div class="callout"> with extra markup to support the style my clients asked for. If the “code” is just simple HTML, I can hide a LOT of implementation details from my clients and “simply” use one of the many WYSIWIG HTML editors out there.

  2. Dan 26 minutes later:

    @Little Fyr: For well understood formats, like XML and HTML there should be no shortage of parsers libraries to use. Figure out the specific tags and attributes, and then write a parser to translate those into objects you use to control the flow of your application.

    For other formats I’d probably use an approach similar to what they use with Cucumber and write a Treetop Grammar (http://treetop.rubyforge.org/) for parsing the DSL source.

    As with most DSLs, parsing is usually the simplest part of problem. The hard part is figuring out what your DSL should look like, making sure it handles all your use cases, and evolving it to handle new ones while remaining clean and simple to use.