RubyConf Day 1: Morning Sessions
Posted by Nick Sieger Fri, 02 Nov 2007 15:35:00 GMT
Marcel Molina: What Makes Code Beautiful?
What is beauty? Marcel explores this topic, starting with posing the question to the audience. “My wife!” Marcel: Why is she beautiful? “Longer answer than you want!”
Marcel comes from a literature/linguistic background, and is interested in how meaning is conveyed, but even beyond the basic words themselves, but the context and expressivity as well.
Note: Marcel has given this talk before.
History of beauty
Pythagoras: was out in the street, heard the blacksmith’s clanging hammer, and was drawn to the noise. He recognized, through closer inspection, that the different sounds that came from the different hammers had relationships, and eventually saw similar relationships in other parts of nature, architecture, and so on.
Thomas Aquinas: Three things that define beauty: 1. Proportion. The economy of size and ratio of parts. The smallest thing that works. 2. Integrity. Well-suited for the purpose. 3. Clarity. Clear and simple.
Each of the qualities are necessary, but none are sufficent. For example proportion (economy) will often clash with clarity. This is especially true in code.
Applied to software
Case study: coercion. Converting XML strings into rich Ruby equivalents. Marcel’s initial solution was a CoercibleString < String
, which used a generator to iteratively try to coerce XML attributes to a number of types, and return the results. ~20 lines of code to convert to 4 types. His second version was a simple class method on String with a case statement.
Kent Beck, in his book Smalltalk: Best Practice Patterns, writes a book about writing good software, but in Marcel’s opinion, arrives at a definition of beauty by describing aspects of code that reflect proportion, integrity, and clarity.
Niels Bohr: “An expert is a person who has made all the mistakes that can be made in a very narrow field.” Marcel calls his CoercibleString a mistake, but one that helped him learn more about coding.
Luckily for us, Ruby is optimized for beauty.
Jim Weirich: Advanced Ruby Class Design
Emphasizing “Ruby” more so than “Advanced”, through three examples that illustrate techniques not commonly found in statically-typed OO languages (Java/C++/Eiffel).
Rake::FileList
FileList['lib/**/*.rb']
FileList sports globbing, a specialized to_s
, and lazy evaluation. First version: class FileList < Array; end
. Good idea, right? Well, with lazy evaluation, resolution of filenames happens only when the list is accessed, not created, so a lot of methods need to be overloaded:
def [](index)
resolve unless @resolved
super
end
The problem becomes that FileList too closely mimics Array, and cannot distinguish itself in the case that matters. So it was changed to delegate to array rather than inherit.
Moral: when you want to mimic built-in classes, it might be better to implement #to_ary
or #to_str
rather than inherit.
Builder::XmlMarkup
What’s the problem here?
b = Builder::XmlMarkup.new
b.student do
b.name "Jim"
b.phone_number "555-1234"
b.class "Intro to Ruby"
end
end
class
is already a method on Object. This begat BlankSlate, which removes unnecessary methods from Object
. Several techniques were applied to eventually arrive at the latest version:
- Use
undef_method
to hide methods that we don’t want. Except, leave methods beginning with double-underscore alone (__id__
and__send__
). - Catch new methods added via a
method_added
hook on Kernel, and anappend_features
hook on Object, to deal with methods defined and modules included after BlankSlate was created
TableNode
Problem: magic conversion of Rails conditions to SQL. An example: User.find(:all).select{|u| u.name == "jim"}
. We don’t really want to load the entire database to do this, but we don’t like writing SQL either.
Solution: Record the actions in the select block by yielding a special TableNode object that captures the method calls and translates to SQL on the fly. Now we can write User.select {|u| u.name == "Jim"}
and have it still execute SQL
- Capture methods called and wrap in a MethodNode to convert to SQL column references
- Capture operators and wrap in a BinaryOpNode to handle
==
,<
, etc.
Clever! Will this work? Here are some issues:
- Small issue -- ordering:
User.select {|u| "Jim" == u.name}
will not work without messing withString#==
. - Bigger issues:
&&
and||
are not override-able in Ruby. What’s worse,!
has pre-defined semantics (in the parser) and cannot be captured.
Lessons learned
- Don’t be afraid to think beyond prior experiences to come up with new ways of solving problems in code.