Advanced Ruby - Day 3

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")
endmethod_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 listeneroutputs:
#<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 listeneroutputs:
#<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
endWe 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 listenerAnd 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 > 5Moving 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.joinDRb 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
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,tdandthelements. 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.@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.