Why Ruby is so much shorter
Chuck:
What features or aspects of Ruby enable it to be half the number of lines as Boo?
Well, there are several aspects of Ruby that allow it to be smaller. Firstly, I’m a better functional coder than I was when I wrote the Boo version, so some of it is certainly because of that. However, several aspects of Ruby make the code much more elegant than it was; I’ll highlight a few in this blog entry.
Using yield effectively
If you judiciously use yield to retrieve extra information from the caller, you can make your code read much better. For example, here’s an excerpt from the main() of my program
library.load file_list do |progress|
puts _("%s%% complete") % (progress * 100.0)
end
library.find_soundtracks do |curname|
print _("’%s’ may be a soundtrack or compilation. Is it? (Y/n) ") % curname
(STDIN.gets =~ /^[Nn]/ ? false : true)
end
list = library.create_action_list(results[:target],
results[:musicformat],
results[:sndtrkformat]) do |tag, invalid|
puts _("The tag ‘%s’ has invalid characters; ‘%s’ are not allowed") % [tag, invalid]
puts _("Please enter a replacement:")
STDIN.gets
end
# …. snip -> in find_soundtracks …
# Call out the handler
@is_soundtrack[curname] = true if yield(curname)
See how clean the main function reads? This kind of thing is really easy and clean in Ruby; in Boo I can do the same thing using delegates but there’s more syntactic nonsense (and even more in C#).
Regular Expressions are built into everything
Regular expression support exists in Boo and it’s better than C#, but Ruby really emphasizes using them in the standard library, whereas in .NET it feels kind of like an add-on. For example, here’s a (inefficient) method to pull all the XML’ish tags from a string (like <artist>/<album> => [“artist”, “album”…)
def get_tags(xml_string)
# Find the tags used in the string
s = xml_string.downcase
tags_used = []
while First_Tag.match s do
tags_used << $1
s.sub! First_Tag, ”
end
tags_used
I’m sure as soon as I post this, someone will chime up with a “That’s a horrible way to do that!”, but my point is, Regular Expressions being built-in to a lot of methods as well as all of the match parts (\1, \2, \3…) being set to global variables like Perl makes life much easier
Dynamic class loading ain’t no thang
In Ruby, it’s even easier than C# or Boo to load new classes, because the “using” statement (“require”) isn’t some special declaration, it’s a statement just like any other. This sample loads all the classes that can dig information out of music files:
# Load the defaults, then the ones specified in the param
paths.each do |x|
Pathname.new(x).each_entry { |y| require File.join(x,y) if y.extname == '.rb' }
end
# Now reflect through the objects and find ones that match
@taggers = []
tagger_classes = ObjectSpace.each_object(Class) do |x|
pm = x.public_instance_methods(true)
next unless pm.include? 'get_tags?' and pm.include? 'song_info'
@taggers < < x.instance
end
@taggers = nil unless @taggers.size > 0
end
Basically in this sample, we load all files in a list of directories, then we dig through all the classes looking for ones that implement the get_tags? and song_info methods. If so, we add the singleton instance to an array (All tag-retrieving classes are Singletons).
Right now, I only have a dummy implementation and one that uses TagLib, but I intend to add a few more to pick up WMA/M4A's, and so all I have to do to implement that is add two more files in those directories.