Not going dark
Rubyforge is now hosting an “initial pre-release of a preview of an alpha of an undocumented proof-of-concept” of the
rewrite gem. More on this presently, but the important (and really the only thing of interest) about rewrite is that when you use rewrite to write:
Person.find_by_last_name("Braithwaite").andand.first_name...You are not opening the Object, Person or Nil classes to add an andand method, nor are you creating a weird temporary BlankSlate object. Rewrite does its thing by rewriting Ruby code: it performs
syntactic metaprogramming, much as Lisp macros rewrite Lisp code.
what problem does rewrite solve?Recall that when you use the “standard” implementation of things like
andand or
try, you are openly modifying core classes like Object.
Therefore, you are reaching out and touching every line of code in your project. You probably aren’t breaking everything, but even if the chance of introducing a bug by adopting something like “try” is infinitesimal for each source code file in your project, the chance grows greater and greater as your application grows.
The problem is that you are introducing a change on Object, and everything depends on Object. This is very different than introducing a change in your code. In that case, only the other bits of code that directly depend on your code are at risk.
Also, imagine if you introduce try and are careful not to break anything. Now somebody else wakes up one day and decides they need a method that works like Prototype’s
Try.these. They call it “try.” They just broke your code, dude! Not only are you making everything dependant upon your version of try, but your code is dependent upon everyone else not breaking try as well. It’s a train-wreck waiting to happen.
Rewrite restricts things like andand or try to your code and your code alone. Sure, if you introduce a bug in your code, you may break things that directly depend on your code. But if you introduce “try” using rewrite instead of modifying Object, you will not reach out across your project and break something entirely unrelated that happens to have defined its own version of try in a completely different way.
how does it work?Rewrite takes your code, converts it to an sexp with Parse Tree, then rewrites the sexp using one or more rewriters you specify. Finally, it converts the sexp back to Ruby with Ruby2Ruby and evals it. It does this when the code is first read, not every time it is invoked, so we mitigate the “do not use andand in a tight loop” problem.
For example, rewrite converts this:
emails.find_by_email(email).try(:destroy)
Into:
lambda { |receiver, method|
receiver.send(method) if receiver.respond_to? method
}.call(emails.find_by_email(email), :destroy)
And this:
numbers.andand.inject(base_sum()) { |total, number| total + number }
Into:
lambda { |__1234567890__|
if __1234567890__.nil?
nil
else
__1234567890__.inject(base_sum()) { |total, number| total + number }
end
}.call(numbers)
Note that with the examples, the names “andand” and “try” completely go away. If someone else defines a try method elsewhere, it will not affect your code because your code never executes a method called try.
the most important open problem to solve in this areaAt the moment that is a huge PITA when creating new rewriters. I don’t think that
it's OK that a language makes it harder for a library creator than for an application developer, so I am working on making it easy to write things like andand or try.
puzzles for language weeniesNote that the andand example above uses gemsym for its parameter while the try example does not.
Why? What could break if it used a name like “receiver?” If you can figure out the salient difference between these two example rewrites, you can probably explain why rewriting try produces exactly the same semantics as the original open classes implementation, but rewriting andand produces a subtle change in semantics.