Andrew Birkett's nobugs.org
I use lots of different programming languages, and they all seem to have different names for the same concepts. For example, string concatenation is “+” in ruby and java, “.” in perl, “,” in smalltalk and “^” in ocaml. After a while, this starts to drive you mad. I know that I want a method which takes “foo” and “bar” and returns “foobar” but I can’t remember which incantation I need to utter today.
Squeak has this neat thing called the MethodFinder (written by Ted Kaehler). It lets you find methods by providing an example. If you’ve got the string “foo”, you can ask the MethodFinder to find all the method that, when called with argument “bar” return “foobar”. This is a very useful tool. No more scrabbling around with documentation to find the name of a method which you know exists. Stay in the red-pill world, and ask the code.
Now, ruby is basically smalltalk (without lots of the k3wl bits). So we can easily build a method finder in ruby too!
Here it is. The first bit is a little gnarly; it works around the fact that FixNums etc are weirdly uncloneable in ruby.
class Object # Clone fails on numbers, but they're immutable anyway def megaClone begin self.clone; rescue; self; end end end class MethodFinder # Find all methods on [anObject] which, when called with [args] return [expectedResult] def self.find( anObject, expectedResult, *args ) anObject.methods.select { |name| anObject.method(name).arity == args.size }. select { |name| begin anObject.megaClone.method( name ).call(*args) == expectedResult; rescue; end } end # Pretty-prints the results of the previous method def self.show( anObject, expectedResult, *args ) find( anObject, expectedResult, *args ).each { |name| print "#{anObject.inspect}.#{name}" print "(" + args.map { |o| o.inspect }.join(", ") + ")" unless args.empty? puts " == #{expectedResult.inspect}" } end end
You can paste these example into the end of the above code, or run “irb -r method_finder” and run them interactively
# Look for string length method; ie. something which returns 5 when called on "hello" MethodFinder.show( "hello", 5 ) # Look for string concatenation MethodFinder.show( "foo", "foobar", "bar" ) # Look for floor function MethodFinder.show( 3.14159, 3 )
These examples give the following output
"hello".length == 5 "hello".size == 5 "foo".<<("bar") == "foobar" "foo".+("bar") == "foobar" "foo".concat("bar") == "foobar" 3.14159.truncate == 3 3.14159.to_i == 3 3.14159.prec_i == 3 3.14159.floor == 3 3.14159.to_int == 3 3.14159.round == 3
Ruby rocks. Smalltalk rocks even more. If you’re going be dynamic, go all the way. :-)
Gnarly things: You can’t clone Fixnums. I can’t find a way to suppress the deprecated warnings. Ruby has weird handling of blocks compared with smalltalk; they’re passed as implicit args and accessed via yield. In Smalltalk, blocks are passed like any other argument.
The internet rocks. Within an hour of posting this, I found that someone has already picked it up and improved it.
I also got an email pointing me towards even more improvements to MethodFinder; support for blocks/varargs, suppress output etc. Very nice.
Please let me know if you enjoyed this article. My blog is where most of my computer-related ideas go.