RubyConf 2007 First Day Morning

I’m at RubyConf 2007 for the next few days. Here’s a stream-of-consciousness blog of the first morning’s talks. Apparently there will eventually be video of the talks online.

RubyConf shirt

Photo by jremsikjr

David Black kicks it off

This year is bigger than ever, with attendance 15 times greater than the first one in 2001. New tracks have been added & the format has been changed with plenary sessions for the mornings and 3 tracks in the afternoon.

Marcel Molina: What Makes Code Beautiful?

Historical definitions of beauty

Beautiful things according to the audience:

  • My Wife
  • “His Wife”
  • Kids
  • Flowers
  • Expressiveness
  • Simplicity

Marcel Molina

Photo by dwortlehock

“Unlike most of the room, I wasn’t doing awesome BASIC hacks when I was 5 [years old]. I was reading books.” Marcel was interested in language and semantics, especially the differences between very similar sentences & constructions. There are good ways of constructing sentences and bad ways, especially for software.

If you make a really long sentence, with lots of relatively, if interesting, long clauses that don’t really do anything but keep the audience from knowing the important bits (because of huge delay and “suspension”), you suck.

Ruby appealed to Marcel almost immediately on some root level, although he wasn’t entirely aware of why. This is half of why “My Wife” is beautiful but you can’t explain why (“I just feel it” versus “her jawline is the golden ratio”). This makes peoples assertions that Ruby is elegant interesting to try to quantify.

Beauty for Software

Three parts of beauty (from Aquinas):

  • Proportion (you could make your hand 10 times bigger and it’d still be ok, but not if you didn’t preserve the ratio of sizes)
  • Integrity (a crystal hammer might be beautiful, but isn’t much of a hammer)
  • Clarity (“complicated in the perjorative sense”)

Now, a case study in code. The background is a web service that reads in a huge chunk of XML and builds Ruby objects as strings, but it’d be really nice to coerce those strings into appropriate objects:

'true'                     => true
'false'                    => false
'42'                       => 42
'2007-08-01T23:55:35.000Z' => Wed Aug 01 23:55:35 UTC 2007

The basic attempt is just to try a bunch of different coercions, one after the other, in a special try {} block.

So, how beautiful is this CoercibleString? It’s fairly proportionate, but that doesn’t really matter, we’re more interested in the appropriate size measure of proportionality. In this case, it’s not the appropriate size, because he later
refactored it to half the number of lines (from ~20 to 10). Does it have integrity, in that it is well suited to what it does? He uses the Generator library, which uses continuations in 1.8 (but threads in 1.9), and it was crazy slow and had a memory leak, so it doesn’t have integrity. Clarity? “Uh… yeah.” It had to be explained to everyone (unlike the refactored). Anyway, it failed
on all three.

Short Code: WTF

Photo by jnunemaker

Remember, all three parts of our definition are necessary (and no one can excluded). You lose clarity if you go too far on shortness:

expand(join("", (map { /\s+\w/ ? ( $_ ....

Does quality relate to beauty

Many engineers seem to not always care about beauty (and “feelings”), great software and beauty go hand & hand. For example, Kent Beck in Smalltalk: Best Practice Patterns is just an exploration of the best ways to design and write software. He might not use the word “beauty” or think about it in that way, but his ideas on rules to write great software is based on the same principles of beauty oulined above.

Is any of this useful?

This refactored coerce method may not be the most beautiful thing on its own, but compared to assembler, it’s stunning.

class String
  def self.coerce(string)
    case string
    when 'true':          true
    when 'false':         false
    when /^[1-9]+\d*$/:   integer(string)
    when DATETIME_FORMAT: Time.parse(string)
    else
      string
    end  
  end  
end

Ruby may not be the most beautiful thing in 20 years, but it certainly is today. If you’re not pleased with the beauty of the case statement above, consider the time before if was implemented in programming languages, then consider how beautiful if was when it was first added.

“Luckily for us, Ruby is optimized for beauty.” “When you’re working on software, try to imagine better modes of expression.” After giving it a try, make sure it doesn’t violate any of the three rules of beauty. Iterate until it passes all three and you’ll hopefully end up with something beautiful.

Hats off to Matz & and the Rubycore team for making such a beautiful language.

Jim Weinrich: Advanced Ruby Class Design

Jim’s history in programming and OO meant that while he’d used dynamic languages, his sense of OO was all from a strict paradigm. Coming from Java and C++ will give you a lot of good concepts, but there are parts of Ruby that are inconceivable in Java.

Jim Weirich

Photo by dwortlehock

Master of Disguise

This is an example from Rake (Rake::FileList).

RUBY_FILES = FileList['lib/**/*.rb']

FileList is like an Array, except that it initializes with a GLOB from the filesystem, has a specialized to_s method, uses lazy evaluation (woot), and has some extra methods (ext (for file extension manipulation), pathmap).

The first pass at this took the similarity to Array and started with that explicitly:

class FileList < Array
 ...
end 

Java would suggest that you never inherit from concrete classes, which also is a good rule for Ruby but for totally different reasons. More on that later.

The lazy bits made direct Array access problematic (like index), so each Array-accessing method had to call the resolve method to unlazyify the FileList. This made some operations not work.

Instead of inheriting from Array, you should use to_ary, so that Ruby helps you when messing with other Arrays out in the world. FileList now is just a regular class not inheriting from anything special and Ruby will ask a FileList if it can behave as an Array (using to_ary).

As for all of the methods needing to call resolve, you can DRY this with a list of relevant methods and a class_eval.

Takeaway: Consider to_ary/to_str when you want to mimic a base class, rather than using inheritance.

Doing nothing

Jim built Builder for the pure fun of it. (Thanks, Jim, it’s a pretty nice library!) It uses block structure and method_missing to make writing XML much easier. However, because XML element names may conflict with builtin methods (class is a good example), we have to make sure xml.class("Intro to Ruby") doesn’t blow up.

Wouldn’t it be nice to inherit from Object without inheriting all the stuff from Object? Introducing BlankSlate, which is really easy to write:

class BlankSlate
  instance_methods.each do |name|
    undef_methods name
  end
end

…but that’s a little too overzealous, because it removes /^__/ methods (__id__ is used by a lot of internal stuff, for example). That can be easily fixed with an unless.

Unfortunately, you’ve still got problems with global methods defined later (in Kernel, say). You can fix this by adding to the method_added hook in Kernel and Object:

alias_method :original_method_Added, :method_added
def method_added(name)
  result = original_method_added(name)
  BlankSlate.hide(name) if self == Kernel
  result
end

All set? Not quite, we’ve still got a similar bug that bypasses method_added:

module Name
  def name
    "My Name"o
  end  
end 

class Object
  include Name
end  

...
xml.name("jim")

This can be fixed with the append_features hook (look at BlankSlate in Builder).

Parsing without Parsing

Consider:

User.find(:all, :conditions =< ["name = ?", "jim"])

…which looks a lot like SQL code. Why can’t I just call select?

user_list.select {|user|
  user.name == "jim"
}

“Wouldn’t it be nice if there was a way we could use select on ActiveRecord models?” Let’s write it (naive first attempt):

class User
  def self.select(&block)
    find(:all).select(&block)
  end  
end  

This, of course, is not effecient at all, and large tables will kill you. Databases do really have a purpose, of course, and we should be using their design. Here’s a magical method:

def self.select(&block)
  cond = translate_block_to_sql(&block)
  find(:all, :conditions => cond)
end 

…however, not many people have written Ruby parsers, which is an “interesting language to parse”. You could use ParseTree, which uses ruby to parse Ruby then evicerates the result. Could we just execute the code? (huh?)

Here’s some curious code:

$ irb -rnode1
>> user = TableNode.new("users")
>> result = user.name
>> puts result.to_s
users.name
>> result2 = user.age
>> puts result2.to_s
users.age

This allows for references to tables that helps build SQL code. Here’s the background (similar for MethodNode):

class TableNode < Node
  def initialize(table_name)
    @table_name = table_name
  end  
  def method_missing(sym, *args, &block)
    MethodNode.new(self, sym)
  end  
  def to_s
    @table_name
  end  
end

OK, we’ve got field references down, but how do we do stuff like ==?

class Node
  def ==(other)
    BinaryOpNode.new("=", self, other)
  end  
end

…well we just capture the interesting method in the Node class then translate the method to a SQL fragment (in BinaryOpNode)

$ irb -rnode1res1 = (user.age == 50)
>> user = TableNode.new("users")
>> puts res1.to_s
(users.age = 50)
>> res2 = (user.name == "jim")
>> puts res2.to_s
(users.name = jim) # oops, no quotes

To quote strings, we need to differentiate between LiteralNodes and StringNodes, which just wraps with quotes (and probably does escaping). Getting the right kind of Node could depend on a case statement, but that’s not very OO. Every object should really know how to convert itself…

class Object
  # be careful opening core classes, which is why we have a unique name
  def as_a_sql_node 
    LiteralNode.new(self)the r
  end
end  

class String
  def as_a_sql_node
    StringNode.new(self)
  end  
end

Now we just need to call it:

  def ==(other)
    BinaryOpNode.new("=", self, other.as_a_sql_node)

end

…and, as we’d hoped:

>> res2 = (user.name == "jim")
>> puts res2.to_s
(users.name = 'jim') 

Problems

We haven’t handled commutativity [hey, Marcel had this problem too!]. "jim" == user.name will not work, although + gets help from coerce for mathematical operators. A killer problem is that &&/|| aren’t methods (by necessity, because of their shortcircutyness). !/!= also have predefined semantics and aren’t overridable. So, this technique really wouldn’t work for SQL. It’s a “solution looking for a problem.”

What did we learn?

Programming languages shape the way you think, so make sure you’re thinking about problems in a Ruby-ish way. Sometimes, the corners of a language will hold the keys to good, idiomatic design. Don’t be afraid of unusual solutions (some of the time).

Comments are closed.