One of the key insights that moved me from Java to Ruby was an “ah ha” (oh crap?) moment I had as I explored the code base of Hibernate through Spring while shooting down a tricky bug. At the time, it struck me how ingenious the coders of the frameworks had to be. Both solutions are examples of metaprogramming, or writing programs that write programs. Hibernate added persistence (as well as a couple of closely related crosscutting concerns like logging, security and transactions) to a pojo primarily through reflection, but also through dynamic proxies (and now, annotations.) Spring adds generic crosscutting concerns and glue code through a variety of design strategies, including callback templates, reflection, proxies, AOP, and now annotations.
These frameworks radically change the way you write Java code by brilliantly extending the language through bolt-on syntax: XML, annotations, AspectJ AOP, and additional tried-and-true interfaces with a smattering of subclassing mixed in.
Then, out of curiosity, I started poking through Rails. I first dug into scaffolding, and found the scaffolding code simply opened up the target class and dumped in methods, with very little code substitution. No black magic. Here’s the code:
module_eval <<-“end_eval”, FILE, LINE verify :method => :post, :only => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ], :redirect_to => { :action => :list#{suffix} } def list#{suffix} @#{singular_name}_pages, @#{plural_name} = paginate :#{plural_name}, :per_page => 10 render#{suffix}_scaffold “list#{suffix}” end def show#{suffix} @#{singular_name} = #{class_name}.find(params[:id]) render#{suffix}_scaffold end def destroy#{suffix} #{class_name}.find(params[:id]).destroy redirect_to :action => “list#{suffix}” end … end_evalAfter reading through the competing Java code that did the same thing, I was spellbound. The module-eval evaluates the code that follows leading up until “end-eval” is encountered. The evaluation process makes substitutions (for #{suffix}, etc), and executes the code. The code adds each of the specified methods to the class. That’s it.
The Java community is not just interested in metaprogramming. We’re obsessed with it. We understand that metaprogramming represents the way to higher productivity and a higher abstraction. The problem is this:
Java is not the best language for metaprogramming!
Java framework developers have to work too hard.
————————-
If you’re a Java developer, you recognize CGLib as a groundbreaking technique that shattered the way we expressed metaprogramming. No more code generation; no more blowing your single shot at inheritance. But Ruby developers would recognize it as a primitive tool that just scratches the surface. Java reflection is cumbersome. Java’s primitives complicate basic metaprogramming services. (Read an XML emitter in Java vs. Ruby.) And other techniques bolt on additional syntax that complicates the language.
Ruby developers also have tools that Java developers don’t. You can override missing method. You can throw in method invocations as you load a class, and each invocation can add its own methods and instance variables. You can use mixins and functional programming. You can take advantage of duck typing. You can leverage the flexible syntax to create better domain specific languages. You can open an existing class. You can alias methods to provide quick interceptors or change the behavior of a class. You can override method-missing.
Java framework developers are dealing from a short deck, and all of the aces are missing.
Consumers of those frameworks have to work too hard.
———————-
Here’s the kicker. After working harder than Ruby counterparts, you’ll produce a framework that’s harder to use than the Ruby version. Your domain specific languages will be cluttered with the noise of Java syntax. You’ll often have to step outside of the language to delay binding. (That’s why so many smart people introduced XML for configuring or even programming frameworks.)
So here’s my straw man.
- Java’s emphasis is squarely on metaprogramming. (EJB3, Spring, Seam, Hibernate, JPA, RIFE, etc)
- Java has a fraction of the metaprogramming power of Ruby.
- People are starting to recognize the value of metaprogramming. (Rails.)
Yet:
- Java has dominant marketshare, even in areas where metaprogramming is the most important.
To me, this does not look like a stable system. A fair amount of rebalancing is inevitable.