github
Advanced Search
  • Home
  • Pricing and Signup
  • Explore GitHub
  • Blog
  • Login

raganwald / rewrite

  • Admin
  • Watch Unwatch
  • Fork
  • Your Fork
  • Pull Request
  • Download Source
    • 23
    • 0
  • Source
  • Commits
  • Network (0)
  • Issues (0)
  • Downloads (0)
  • Wiki (1)
  • Graphs
  • Branch: master

click here to add a description

click here to add a homepage

  • Branches (2)
    • master ✓
    • representation_independent
  • Tags (0)
Sending Request…
Enable Donations

Pledgie Donations

Once activated, we'll place the following badge in your repository's detail box:
Pledgie_example
This service is courtesy of Pledgie.

Code rewriting including rewrite-by-example and call-by-name semantics for functions — Read more

  cancel

  cancel
  • Private
  • Read-Only
  • HTTP Read-Only

This URL has Read+Write access

RewriteRails 
raganwald (author)
Fri Jan 09 11:59:39 -0800 2009
commit  8244dd3338acb128348571981c34004544013ebd
tree    d373daaec455567e1f4a33df35e9c9d676f5440c
parent  02f24ed834b6a2b0251f010b41801170bb265424
rewrite /
name age
history
message
file License.txt Loading commit data...
file Manifest.txt
file README.textile
directory lib/
directory pkg/
README.textile

rewrite

→ ‘rewrite’

Breaking News

The rewrite gem is not in active development at this time. Instead, I am working on RewriteRails. RewriteRails brings Rewrite’s functionality to Ruby on Rails projects.

What

Rewrite adds sexp-rewriting metaprogramming to Ruby. Or in plain English, Rewrite is a collection of features for Ruby programming like an implementation of andand. What makes Rewrite’s versions of these features different and possibly useful is that they are implemented by rewriting your Ruby code rather than by opening up classes like Kernel, Object, Nil, or Symbol.

Installing

sudo gem install rewrite

The basics

In your environment.rb file (Rails) or at the top of your source code file, include Rewrite::With and Rewrite::Prelude:

include Rewrite::With
include Rewrite::Prelude

This makes the with method available to your souce code, so you can write:

with(andand) do
	...
	first_name = Person.find_by_last_name('Braithwaite').andand.first_name
	...
end

The special magic here is that instead of opening up the Object and Nil classes to add #andand, Rewrite rewrites the ruby code inside the do/end block so that it looks roughly like this:

with(andand) do
	...
	first_name = lambda { |__1234567__|
		if __1234567__.nil?
			nil
		else
			__1234567__.first_name
		end
	}.call(Person.find_by_last_name('Braithwaite'))
	...
end

That’s a big win because you can use the metaprogramming features you want without worrying that you are breaking anybody else’s code. If you use an implementation of #andand that relies on opening the Object and Nil classes, how do you know you aren’t (a) breaking some other code somewhere else, or (b) exposing your code to being broken if something else changes the #andand methods?

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.

called_by_name

See Macros, Hygiene, and Call By Name in Ruby and With, Unhygienic, and Call-by-Name Semantics in Rewrite.

Unhygienic rewriting

A new feature every much like a new human being: Vulnerable, disruptive, and a complete mess:

include Rewrite::With

andand = Rewrite::ByExample::Unhygienic.
  from(:receiver, :message, [:parameters]) {
    receiver.andand.message(parameters)
  }.to {
    lambda { |andand_temp|
      andand_temp.message(parameters) if andand_temp
    }.call(receiver)
  }

with (andand) do
	# ...
	foo.andand.bar(:bash, blitz(5))
	# ...
end

becomes:

# ...
lambda { |andand_temp|
  andand_temp.bar(:bash, blitz(5)) if andand_temp
}.call(foo)
# ...

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.

How to love Rewrite without destroying Ruby

If you write:

include Rewrite::With
include Rewrite::Prelude

...

in Kernel context (such as in Rails’ environment.rb), you are adding methods to the global namespace. You can include Rewrite in one or more classes where you want to use its features:

class Person
	include Rewrite::With
	include Rewrite::Prelude
	
	...
end

Or you can go 100% safe and avoid adding anything to existing namespaces:

class Person

	Rewrite.with(Rewrite::Prelude::Andand.new) do
	   ...
	end
end

It’s entirely up to you.

What goodies do I get?

Check the rdocs, specifically the docs for Rewrite::Prelude.

Current Status

This is just a proof-of-concept at the moment. Writing new features is torturous, so I’m not going to suggest you write your own until I figure out how to make that easy.

Build and test instructions

cd rewrite
rake test
rake install_gem

More background

An interview where I discussed rewrite and My presentation slides from RubyFringe.

License

This code is free to use under the terms of the MIT license.

Contact

Comments are welcome. Send an email to Reg Braithwaite email via the forum

Blog | Support | Training | Contact | API | Status | Twitter | Help | Security
© 2010 GitHub Inc. All rights reserved. | Terms of Service | Privacy Policy
Powered by the Dedicated Servers and
Cloud Computing of Rackspace Hosting®
Dedicated Server