Irb is a Text Adventure
December 16th, 2009
Ruby's flavor of classed OO offers a thoughtful balance of rigidity and flexibility along with plenty of introspection and reflection. Over time I have come to experience this model less as a framework for thought and more as a living thing with which to converse. Dave Thomas brought up the prospect of a Ruby object browser over nine years ago. That thread raised questions that still haven't been answered.
Unfortunately for a browser the rigidity offered by Ruby is optional. I personally feel that HTML with links is the only graphical form that comes close to capturing the needed range. I just described a blank slate for a digraph so feel free to think it's lame. The object browser is possible and desirable but after all these years Irb is still the Ruby browser of choice.
My take is that every object in Irb is like a noun in a text adventure, and likewise every object is different. There are common verbs and uncommon verbs, and not every noun supports every verb. The components of trial, error, failure, and reward are the same. Nothing ruins my suspension of disbelief like having to consult documentation, and I like to believe that I am programming for as long as possible. The notable missing link to the underground world of Zork is "look". Here is "look". It is nothing more than a mashup of introspection methods but I've found it indispensable.
# Examples:
# foo.look
# foo.class.look
# foo.method(:meth).look
# foo.method(:meth).source_context
# foo.instance_variable_hash
Object.class_eval do
def instance_variable_hash
ret = {}
instance_variables.each { |v|
ret[v] = instance_variable_get(v)
}
ret
end
def look
{
:class => self.class,
:interesting_methods => (methods - Object.new.methods).sort,
:instance_variables => instance_variables.sort,
}
end
end
Class.class_eval do
def look
{
:class => self.class,
:ancestors => ancestors,
:included_modules => included_modules,
:interesting_methods => methods - Class.new.methods,
:instance_methods => instance_methods(false),
:class_variables => class_variables,
:instance_variables => instance_variables,
:constants => constants,
:source_files => methods(false).collect { |m| method(m).source_location.try(:first) }.compact.sort.uniq,
}
end
end
Module.class_eval do
def look
{
:class => self.class,
:ancestors => ancestors,
:included_modules => included_modules,
:class_variables => class_variables,
:interesting_methods => methods - Class.new.methods,
:instance_methods => instance_methods(false),
:instance_variables => instance_variables,
:constants => constants,
}
end
end
Method.class_eval do
if RUBY_VERSION < "1.9"
def source_location
end
end
def look
{
:class => self.class,
:arity => arity,
:name => name,
:owner => owner,
:receiver => receiver,
:source_location => source_location,
}
end
def source_context
return nil unless source_location
line_number = source_location.second
file = File.open(source_location.first, "r")
lines = file.lines.drop(line_number - 1)
indent = lines.first.scan(/^ */).first.length
end_marker = Regexp.new("^" << " " * indent << "end")
interesting_lines = []
begin
line = lines.shift
interesting_lines << line
end until line =~ end_marker or interesting_lines.count > 20 or line.nil?
file.close
source_location << interesting_lines.join
end
end
Plays well with 1. Wirble, 2. pretty printing, 3. Ruby 1.8, 4. Ruby 1.9, and OF COURSE your .irbrc.
An Introduction to Closures in Ruby
September 6th, 2007
More experimenting with closures and iteration led to some interesting results. Due to some quirks with Ruby's scope rules there are a few gotcha's and some of my initial assumptions about how variables are bound were wrong. Hopefully this entry will serve to enlighten those who, like me, wondered about the mystical Ruby closure.
What are closures?
Closures are an stateful object created on the fly when a block of code with its own scope becomes bound to variables in its environment. Closures consist of two elements. The first is a reference to some executable code such a block, proc, or method. The second is the set of variables that are bound to the scope of the executable block. This code creates a closure bound to c: n = 2; c = lambda { n * n }; A closure is created any time a lambda references variables not local in scope. To be perfectly clear a closure is loosely:
struct Closure {
void *function;
void *function_arguments;
};
What problem do closures solve?
Read the rest of this entryClosures vs Function Objects (Functors) in Ruby
September 3rd, 2007
When a proc or lambda is created the variables in local scope are bound and not copied. This means that if an outside method updates a variable the closure will see it, and if the closure updates a variable the method will see it. For example:
closures = []
for n in 0..7
closures << lambda { n }
end
closures.map { |c| c.call } => [7, 7, 7, 7, 7, 7, 7, 7]
The variable n was updated outside the { n } closure. All eight closures will print 7 in this example since that was the last value of n. What if I need to keep a unique value of n for each closure? There’s a trick, make a function object. Here’s the solution:
def make_functor(n)
lambda { n }
end
closures = []
for n in 0..7
closures << make_functor(n)
end
closures.map { |c| c.call } => [0, 1, 2, 3, 4, 5, 6, 7]
Because make_functor’s argument (n) goes out of scope when make_functor returns it (n) can no longer be updated. This effect isolates the variable in the closure from the rest of the program and the variable n is not garbage collected because it is still referenced by the closure.
Now lets apply this to a real world problem. With the ruby-gnome2 library I am writing a class that displays a collection of ActiveRecord objects as an editable grid. Because ruby-gnome2 is a close match to the C bindings there is quite a bit of record keeping to do. Here is a loop that will add an “edited” signal handler to each column in a Gtk::TreeView. This code is buggy because n is shared between all closures created when signal_connect internally calls to_proc on the provided block. An edit to any cell will result in that change getting applied to only the cell in the last column since the loop exits with n on the last column.
for n in 0...NUM_COLUMNS
renderer.signal_connect "edited" do |renderer, path, data|
iter = @tree_store.get_iter path
iter[n] = data
end
end
Functors to the rescue!!!
def connect_edit_functor(renderer, n)
renderer.signal_connect "edited" do |renderer, path, data|
iter = @tree_store.get_iter path
iter[n] = data
end
end
for n in 0...NUM_COLUMNS
connect_edit_functor(renderer, n)
end
Here we pass the relevant variables to a method that will bind the signal handler and then gracefully let those variables go out of scope before the loop updates n. The result is that each instance of the signal handling closure now holds its own copy of n at the correct value. The correct column will therefore be referenced when the “edited” signal invokes the callback.
Proc.new vs Lambda in Ruby
March 19th, 2007
I found the following lines of code on Wikipedia today. It’s a very succinct description of one important difference between a lambda and a Proc. Try printing the return value of f.call for more insight.
def foo
f = Proc.new { return "return from foo from inside proc" }
f.call # control leaves foo here
return "return from foo"
end
def bar
f = lambda { return "return from lambda" }
f.call # control does not leave bar here
return "return from bar"
end
puts foo # prints "return from foo from inside proc"
puts bar # prints "return from bar"
Whole article on Wikipedia about closures. . . And a bunch more on procs and blocks
Gametrees in Ruby
December 28th, 2006
Here is a Ruby implimentation of Tic Tac Toe, or Noughts and Crosses if you prefer. Just paste the following code into irb and type either me_first or you_first.
Read the rest of this entry