Tuesday, January 30, 2007

Is eval really necessary?

Last week, I went to The Rails Edge mini-conference for $WORK. The event started at a pretty high tempo, with Dave Thomas’ talk on Metaprogramming Ruby. It’s a common topic in the Ruby community, and hyper-relevant at any Rails event, since Rails is all about metaprogramming.

Ruby provides a small but complete set of tools for metaprogramming: blocks (closures), lambda, define_method, and various forms of eval. However, Dave was quick to mention that eval is generally considered to be bad form, and should be used sparingly, when no alternative exists. Ruby also has a few nifty features that simplify metaprogramming, like an extensive set of hook methods and open classes.

Perhaps the biggest example of Ruby metaprogramming in action is the Rails ActiveRecord class. ActiveRecord works though inheritance, where the base class intuits the name of the table it models (based on the database used in the current project’s environment), and figures out the rest as needed.

Here’s a rudimentary ActiveRecord model:

class User < ActiveRecord::Base

If there are additional relationships in the database, those relationships can be added by invoking (inherited) class methods on the model, in a rather declarative fashion:

class User < ActiveRecord::Base
has_many :purchases
belongs_to :group

All the details are filled in through inheritance, method_missing, and/or direct modification of the subclass. Only through a strict discipline of metaprogramming is this magic possible.

But that’s not the big insight. The big insight is that all of this deep magic can be done without eval.

The big win here is metaprogramming, and Ruby and Rails demonstrate together that you don’t (always) need eval or macros to do sophisticated metaprogramming. Ruby’s tools boil down to various kinds of closures: blocks and lambda produce proc-flavored closures, while define_method creates a closure that is explicitly attached to a receiver that will provide a self reference when invoked.

It wasn’t until this point that I realized that Haskell’s support for metaprogramming was as complete and full featured as Ruby’s, and that eval isn’t strictly necessary for doing complex code generation. Even though Haskell doesn’t have eval, it does have lambdas, type classes, higher order functions, combinators, sections, partial application, and many more primitive operations that support metaprogramming. Not only do these features make metaprogramming possible, they make it pervasive all the way down to the prelude.

And for those times when Haskell’s various flavors of closures don’t do the trick, there’s always Template Haskell (which, I have to admit, I’ve never needed to use).

1 comment:

TuringTest said...

has a library that provides eval:

"Thirdly, a combination of runtime compilation and dynamic loading provides a set of eval functions- a form of runtime metaprogramming."