Thursday, August 24, 2006

» Doing it the Ruby Way™ +

I posted a comment on Redhanded that got me thinking about the Ruby Way™ of doing string formatting. Since ruby is a true OO language, where everything is a first-order object, and all methods are object attributes, it doesn't seem to follow suite to leave it at Kernel#format which takes as its first argument the string object on which to operate. It seems like it would be much more rubyish to have format (and sprintf) as methods of the String class which operate on the caller, the same way that the % method does.

So that is simple enough to implement:

class String
define_method(:format) { |*args| Kernel.format(self, *args) }
alias_method(:sprintf, :format)
end

a, b = 'string', 2
s1, s2 = 'This is a %s', 'A %s and a %d'
#these all do the same thing
#literal
p(s1 % a)
p(s2 % [a, b])
#format
p(s1.format(a))
p(s2.format(a, b))
#sprintf
p(s1.sprintf(a)
p(s2.sprintf(a, b))

But wait...what about Kernel#p and so forth? Why are we using them from the top-level rather than as a method on the object? Well, for one thing, the print-like methods accept different types of objects as input. But that means they must be polymorphic -- that's just a fancy way of saying that they know what types of data they are operating on and treat each in a specific way. Strings are printed with double-quotes, arrays are printed as ["member", "member", ...], while hashes are printed as {"key"=>value, ...}. (Note, this only describes the p function, others behave slightly differently: try p, puts, and print with different data-types to get an idea of how they each work). So we can easily add these methods to Object without any extra work needed to determine data-types and so forth. We can even take arguments to them so that multiple objects can be printed at once, just as the Kernel methods allow.

class Object
define_method(:p) { |*args| Kernel.p(self, *args) }
define_method(:puts) { |*args| Kernel.puts(self, *args) }
define_method(:print) { |*args| Kernel.print(self, *args) }
alias_method(:say, :puts)
end
class String
define_method(:format) { |*args| Kernel.format(self, *args) }
define_method(:printf) { |*args| Kernel.printf(self, *args) }
alias_method(:sprintf, :format)
end

Now we can do stuff like this:

a = 'I like %s'
b = 'ruby'
a.format(b).print(" alot!\n")
['I', '[heart]', 'ruby', '!'].puts({"me"=>" too!"})

Fun. :)

Addendum: I was just informed of Object#display, which does basically the same thing as my Object#puts does. Nifty! ruby is Chock Full O'Goodness. :)

0 Comments:

Post a Comment

<< Home