A programmer I admire greatly twittered the following today:
Dear Ruby haters: I used to be afraid of the table saw until I learned how to use it safely. This did not involve nerfing the table saw.
I have great respect for this man, but we have a fundamental disagreement about a language he uses by choice and I use by necessity. I have a bunch of
(
Read more... )
This is possible. For a long time I tried hard to write Python in Ruby, but I think I'm pretty familiar with the idioms these days.
adding an arbitrary method at runtime [...] is messy and hard
I may be heading down the wrong path, but the last time I wanted to write a method that added another method to a class, I had to use module_eval(). Here's a silly example of an extensible RPC class:
class MyClass
def self.client_api(name, *args)
module_eval(<<-End, __FILE__, __LINE__ + 1)
def #{name}(*myargs)
remote_call("#{name}", #{args.inspect}, myargs)
end
End
end
def remote_call(name, argnames, args)
str = "Remote call: #{name}("
str += argnames.zip(args).collect { |k,v|
"#{k}=#{v.inspect}"
}.join(", ")
str += ")"
puts str
end
client_api :sum, :operands
client_api :difference, :lhs, :rhs
end
foo = MyClass.new
foo.sum([1,2,3])
foo.difference(7, 3)
Apart from the fact that this heredoc (stolen from net/https.rb) screws with my editor's indentation settings, I'm really uncomfortable having to module_eval() a string rather than just using a closure as I would in Python.
Further Googling on the topic turns up define_method() which I did not previously know about and which appears to be documented only in blogs and forums. This makes the thing much cleaner:
def self.client_api(name, *args)
define_method(name) do |*myargs|
remote_call(name, args, myargs)
end
end
Thus removing most of my objections and replacing them with "why the hell is nobody using this where I can see it?" Of course, method_missing could easily be used in this situation, but then I can't introspect my API so that's right out.
Reply
And THAT, even from my "but the kool-aid tastes fantastic" perspective, is the single biggest pain point in Ruby.
It's getting exponentially better every year. Back in 2004 it was excruciating--if it wasn't in the Pickaxe book, you had to know Japanese to find any docco for it.
I need to release/publish/blog my define_class_method code. THAT is a method that does not, in fact, exist. Most Rubyists skip over it because the implementation is a one-liner. I feel it should be made explicit because it's a fairly cryptic one-liner, and two days of Googling did not lead me to the solution. It was only after writing a replacement and refactoring it and discovering the name "define_class_method" through my refactoring (because that's what my code *did*) that I thought to google for define_class_method, and thus found the answer.
P.S. You definitely want the define_method version if at all possible. _eval code is MUCH slower at runtime. Because Ruby can't know which parts of your string will change, and can't infer meaning from it, it can't optimize the compilation of your string. It will eval() it every single time. (At least, MRI does this. Dono if JRuby or Rubinius suffer from this as well. I suspect so, however.)
P.P.S. sudo gem install kitchensink to get my kitchensink library. It's essentially my accidental reinvention of facets, only much, much smaller. Look in lib/kitchensink/patches/object.rb for the define_class_method method.
Reply
Combine this with TIMTOWTDI and the positive-reinforcement of people using the bad way of doing things and you get even more pain. If module_eval() didn't exist or was slapped down soundly by the community except where it was truly necessary (and the same for method_missing() which is the single most inappropriately used Ruby feature), the better features would be used more often and we would get positive-reinforcement in the right direction.
I'll take a look at kitchensink. :-)
Reply
P.S. +1 for define_method. Compile-time syntax checking is a good enough reason to use it even if _eval wasn't slower.
Reply
Leave a comment