Pimp my (Java) methods
A friend of mine has been playing around with Scala and asked about its meta-programming ability. In particular, he’s a Ruby fiend, so is used to its nice reflection abilities. Specifically he wanted to be able to explore an API interactively from the console, like the following for an integer in Ruby:
>> 1.class => Fixnum >> 1.methods => ["%", "inspect", "<<", "singleton_method_added", "&", "clone", ">>", "method", "round", "public_methods", "instance_variable_defined?", "divmod", "equal?", "freeze", "integer?", "chr", "*", "+", "to_i", "methods", "respond_to?", "-", "upto", "between?", "prec", "truncate", "/", "dup", "instance_variables", "__id__", "modulo", "object_id", "succ", "|", "eql?", "zero?", "require", "~", "id", "to_f", "singleton_methods", "send", "prec_i", "taint", "step", "to_int", "frozen?", "instance_variable_get", "__send__", "^", "instance_of?", "remainder", "to_a", "+@", "nonzero?", "-@", "type", "**", "floor", "<", "protected_methods", "<=>", "instance_eval", "==", "prec_f", "quo", ">", "display", "===", "downto", "id2name", "size", "instance_variable_set", "kind_of?", "abs", "extend", ">=", "next", "to_s", "<=", "coerce", "hash", "ceil", "class", "tainted?", "=~", "private_methods", "gem", "div", "nil?", "untaint", "times", "to_sym", "[]", "is_a?"] >> Fixnum.methods => ["inspect", "private_class_method", "const_missing", "clone", "method", "public_methods", "public_instance_methods", "instance_variable_defined?", "method_defined?", "superclass", "equal?", "freeze", "included_modules", "const_get", "methods", "respond_to?", "module_eval", "class_variables", "dup", "protected_instance_methods", "instance_variables", "public_method_defined?", "__id__", "object_id", "eql?", "const_set", "require", "id", "singleton_methods", "send", "class_eval", "taint", "frozen?", "instance_variable_get", "include?", "private_instance_methods", "__send__", "instance_of?", "private_method_defined?", "to_a", "name", "autoload", "type", "<", "protected_methods", "instance_eval", "<=>", "==", ">", "display", "===", "instance_method", "instance_variable_set", "kind_of?", "extend", "protected_method_defined?", "const_defined?", ">=", "ancestors", "to_s", "<=", "public_class_method", "allocate", "hash", "class", "instance_methods", "tainted?", "=~", "private_methods", "gem", "class_variable_defined?", "induced_from", "nil?", "untaint", "constants", "autoload?", "is_a?"]
Unfortunately Scala’s meta-programming abilities are currently limited to Java’s, which is to say not very good. As I’m in Rails-land at the moment, I asked Tony to have a crack at it, to which, he’s obliged:
class Classs[T](c: Class[T]) {
lazy val methods = scala.collection.immutable.HashSet(c.getDeclaredMethods: _*) ++ c.getMethods
}
object Classs {
implicit def c[T](c: Class[T]) = new Classs(c)
}
Here’s how to use it, we ask for all of Integer’s methods that start with a “t”:
scala> classOf[Integer].methods.filter(_.getName.startsWith("t")).foreach(println(_))
public static java.lang.String java.lang.Integer.toBinaryString(int)
public static java.lang.String java.lang.Integer.toHexString(int)
public static java.lang.String java.lang.Integer.toOctalString(int)
public java.lang.String java.lang.Integer.toString()
public static java.lang.String java.lang.Integer.toString(int,int)
public static java.lang.String java.lang.Integer.toString(int)
private static java.lang.String java.lang.Integer.toUnsignedString(int,int)
Tony’s original source: http://fprogramming.org/paste/viewpaste.php?id=107.
The problem is that you’ve now lost the method ordering, which is often useful. Instead of HashSet, you should probably use a List of some kind.
Daniel Spiewak
22 Apr 08 at 3:32 am
The ordering is not guaranteed by the reflection APIs, to quote from
Class.getDeclaredMethods:But yeah, I agree that ordering would be nice if we could get it.
On a side note, I really don’t want to be writing this stuff, it should be part of the standard APIs.
Tom Adams
22 Apr 08 at 9:02 am
Does scala support dynamically adding methods to a class ? (static / instance) methods the way Groovy does.
I want to be able to add methods to a class without extending it - the way prototype library adds methods to built-in javascript implicit objects. Ofcourse groovy and ruby allows that too?
karthik
23 Apr 08 at 2:49 am
Not as such, however Scala has something called implicit defs (used in the post above) that achieve the same effect, but in a safe manner, i.e. you can more easily reason about the effects an implicit will have, vs. not knowing what does what in Ruby/Groovy.
So when you call a function on a type that doesn’t have that particular method defined, the compiler looks to see if there are any implicits in scope that can convert from the type in question, to a type that has the method defined.
This is why
classOf[Integer].methodsworks, there is nomethodsfunction on theClass[T]type, however there is one on theClasss[T]type, and an implicit exists to get between the two types.Tom Adams
23 Apr 08 at 8:20 am
Still, the Ruby version is less verbose to type, and imho is more elegant. This is not to say that we cannot have a library in Scala/Java that is just as elegant to use.
Abdul Habra
23 Apr 08 at 11:56 pm
The Ruby version is less typing, because it is less elegant. That is, it’s not the same thing; it’s a weaker, less useful and less practical representation as per the universal type system.
You can’t have something for nothing.
Tony Morris
5 May 08 at 4:58 pm
You can add a “methods” method to Any to achieve pretty much the same thing in Scala. You can format it however you like, but I like seeing each method on a new line as it helps me read the arguments:
http://minformix.org/blog/index.php?/archives/91-Scala-Querying-an-objects-fields-and-methods-with-reflection.html
Min Huang
11 Jun 08 at 6:32 pm