InfraRuby Vision delivers solutions for mobile and web applications.
You can try InfraRuby live in your browser!
Try the InfraRuby statically typed Ruby compiler live in your browser. Try our examples or write your own code.
Follow @InfraRuby on Twitter for news and updates.

Ruby metaprogramming with s-expressions

Metaprogramming is code writing code. Usually metaprogramming in Ruby is done using the reflection features of the language: define_method, instance_variable_get, and others. In this post you will see another way to do metaprogramming that has some advantages over the traditional approach.

An example of metaprogramming in Ruby is attr_reader, which defines a reader for an instance variable. In the following class attr_reader defines readers for @x and @y:

class XY
	attr_reader :x, :y
end

That code is functionally equivalent to this code, which defines readers explicitly:

class XY
	def x
		return @x
	end

	def y
		return @y
	end
end

You could* implement attr_reader in Ruby using define_method and instance_variable_get:
* but don’t do that! Ruby interpreters optimize attr_reader as a special case.

class Module
	def attr_reader(*names)
		names.each do |name|
			iv_name = "@#{name}"
			define_method(name) do
				instance_variable_get(iv_name)
			end
		end
		return
	end
end

This is the traditional approach to metaprogramming in Ruby: using the reflection features of the language, which take effect when the interpeter executes the program.

But another way to do metaprogramming is to generate Ruby source code, using the meta-ruby gem. The meta-ruby gem generates Ruby source code from s-expressions. The generated code can be written to a file to be read by a Ruby interpreter.

To install the meta-ruby gem, type in a shell:

gem install meta-ruby

Here is an example of s-expressions to define a class with readers:

## <>
class Generator < MetaRuby::Generator
	## -> MetaRuby::Sexp
	def initialize_sexp
		s(:defn, "initialize", s(:args, "x", "y"),
			s(:iasgn, "@x", s(:lvar, "x")),
			s(:iasgn, "@y", s(:lvar, "y")),
			s(:return),
		)
	end

	## String -> MetaRuby::Sexp
	def attr_reader_sexp(name)
		s(:defn, name, s(:args),
			s(:return, s(:ivar, "@#{name}")),
		)
	end

	## -> MetaRuby::Sexp
	def xy_class_sexp
		s(:class, "XY", nil,
			initialize_sexp,
			attr_reader_sexp("x"),
			attr_reader_sexp("y"),
		)
	end
end

You can generate Ruby source code for the XY class like this:

g = Generator.new
d = MetaRuby::Directory.new
d.instance_eval do
	write("XY.rb", g.xy_class_sexp)
end

Try the InfraRuby statically typed Ruby compiler live in your browser. You can use InfraRuby for your own projects with our free download!

Follow @InfraRuby on Twitter
Copyright © 2011-2017 InfraRuby Vision Limited