Thursday Night

Paul Betts’s personal website / blog / what-have-you

Why Ruby is so much shorter

comments

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

# Process the library
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”…)

First_Tag = /(< \S*?>)+[^< ]*/
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:


def load_taggers(*paths)
    # 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.

Written by Paul Betts

January 17th, 2007 at 1:09 pm

Posted in Mono / .NET, Ruby

Leave a Reply