QAPrototype (#91)

The three rules of Ruby Q.:

  1. Please do not post any solutions or spoiler discussion for this quiz
    until
    48 hours have passed from the time on this message.

  2. Support Ruby Q. by submitting ideas as often as you can:

http://www.rubyquiz.com/

  1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby T. follow the discussion. Please reply to the original quiz
message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Caleb T.

I remember playing with some old AI programs of which you could ask
questions
and if it didn’t know the answer, it would ask you what the answer is:

Hi, I'm an AI program.  What's your question?

>> How are you today?

I'm afraid I don't know the answer to that.  Please tell me what I 

should say.

>>> Just fine, thanks.

Okay, I will remember that.  Please ask a question.

>>> How are you today?

Just fine, thanks.
Please ask another question.

This got me thinking about an interesting concept in Ruby.

Your Quiz: Write a Ruby module that can be mixed into a class. The
module does
the following: upon receiving a method call that is unknown to the
class, have
Ruby inform the user that it doesn’t know that particular method call.
Then,
have it ask the user for Ruby code as input to use for that method call
the next
time it is called.

Example:

> object.some_unknown_method

some_unknown_method is undefined
Please define what I should do (end with a newline):

>> @foo = 5
>> puts "The value of foo is #{@foo}"
>>

Okay, I got it.

>>> object.some_unknown_method
"The value of foo is 5"

[Editor’s Note:

I envision this could actually be handy for prototyping classes IRb.
Bonus
points if you can later print the source for all methods interactively
defined.

–JEG2]

Ruby Q. [email protected] writes:

[Editor’s Note:

I envision this could actually be handy for prototyping classes IRb. Bonus
points if you can later print the source for all methods interactively defined.

–JEG2]

Yeah, I was thinking about that as I read this quiz - the core of the
solution seems obvious, though I’m sure we’ll see a bunch of
variation. What really seems interesting to me, though, is the
different ways you could extend this with little helper methods that
are useful when you’re writing a prototype method, such as:

def warn_caller_obsolete(current_meth,args)
call_line = caller[1]
called_meth = caller[0].gsub(/^[^]*([^‘]+)’.
/,‘\1’)
warn “#{call_line}: #{called_meth}' is obsolete; use #{current_meth}'”
self.send(current_meth,*args)
end

Then, you can do things like this:

irb(main):039:0> def b(f); [[f],f]; end;
irb(main):040:0* def a(f); warn_caller_obsolete(:b,f); end;
irb(main):041:0* a(9)
(irb):41:in irb_binding': a’ is obsolete; use `b’
=> [[9], 9]

(Say, whatever happened to the call_stack RCR?)

Similar helper methods could do things like simply log the call for
later (assuming the caller can handle getting “nil” back) or delegate
the call along to some other object, possibly while logging the fact.
(Okay, that last one is probably not of much use)

I can imagine a future ruby IDE that would give the option to pop open
an editor when this happened.

Well, here is my solution. I kind of surprised myself at how short it
is, but
then, there are many ways I could make this much better. The biggest
problem
is that it cannot deal with arguments to the methods. It uses a global
to
keep track of the interactively coded methods, I just couldn’t get it to
work
any other way. I guess this is because modules/mixins are not supposed
to
have instance variables or keep track of their own state. The
print_method
function will break if your method is a one-liner. I know I could do
some
case-analysis to take care of this but I am a lazy, lazy man.

Here is a brief irb session demonstrating it, the code follows…
#########################################

load ‘quiz91.rb’
class Test; include MethodMaker; end
test = Test.new
test.foobar()
No such method: foobar
Care to define foobar? (y/n) y
Enter method definition ([ctrl-d] when done):
first = “Ruby”
middle = “is teh”
last = “roXX0r”
puts “#{first} #{middle} #{last}”

=> nil
test.foobar()
Ruby is teh roXX0r
=> nil
test.print_method(“foobar”)
def foobar
first = “Ruby”
middle = “is teh”
last = “roXX0r”
puts “#{first} #{middle} #{last}”
end
=> nil
test.another_one()
No such method: another_one
Care to define another_one? (y/n) y
Enter method definition ([ctrl-d] when done):
a = 4
b = 5
c = 67
return a + b + c

=> nil
x = test.another_one()
=> 76
x
=> 76
test.print_method(“another_one”)
def another_one
a = 4
b = 5
c = 67
return a + b + c
end

#########################################

module MethodMaker
$imethods = Hash.new

def method_missing(method_name)
puts “No such method: #{method_name}”
# It might be a simple typo…
# so give a chance to bail out.
print "Care to define #{method_name}? (y/n) "
if $stdin.getc == 121 # ‘y’
prompt_method(method_name)
else
raise NoMethodError, “#{method_name}”
end
end

def prompt_method(name)
puts “Enter method definition ([ctrl-d] when done):”
meth = “def #{name}”
while $stdin.gets
meth += $_
end
meth += “end”
meth = meth.gsub("\n",";")
$imethods["#{name}"] = meth
eval meth
end

def print_method(name)
meth_Array = $imethods[name].split(";")
puts meth_Array[0]
meth_Array[1…meth_Array.size-2].each { |line| puts " #{line}" }
puts meth_Array[-1]
end
end

########################################

-d

@Darren:

Actually, you can declare an attr in the module and it will work as an
instance variable in the class it’s mixed in. For example:

module QAPrototype
attr :_methods_added
end

That’s what I did.

Also, print does work with one liners, but you need to flush out the
buffer each time you print the one line with
$stdout.flush. (Thanks to to cool folks in #ruby-lang for
that info.)

I’m still working on my solution (I’ve had quite a busy weekend) but
I’m having some trouble figuring out how to remove the methods I’ve
added… It’s odd: method_missing is called for things like
#class_eval and #remove_method. O_O So, I might be doing something
wrong just adding the methods in #instance_eval.

I’ll work on it some more and post it later today.

M.T.

Here is my solution, it focuses on being able to use the methods you
input to patch the original file.
For inputting methods, you need to give the entire method declaration
(or any code that adds it, like attr_*).
The script automatically detects when you’re done, except when you
have a syntax error (then you need to force input with “END”).

An irb session:
91test.rb is a file with

require ‘inputmethod.rb’
class Test
include InputMethod
end

irb -r91test.rb
irb(main):001:0> t = Test.new
irb(main):002:0> t.foo
Give the method definition for Test#foo
END to force input end, display error and return nil
def foo
bar
end
Give the method definition for Test#bar
END to force input end, display error and return nil
attr_accessor :bar
=> nil
irb(main):003:0> t.bar=42 ; t.foo
=> 42
irb(main):004:0> puts Test.patches
def foo
bar
end

attr_accessor :bar
irb(main):005:0> Test.remove_patch :foo
irb(main):006:0> t.test
Give the method definition for Test#test
END to force input end, display error and return nil

def test(x=42)

if x < 20
“smaller”
else
“greater or equal”
end
end
=> “greater or equal”
irb(main):007:0> Test.patch!
Adding methods bar,test to Test

91test.rb is now:
class Test
attr_accessor :bar

def test(x=42)

if x < 20
  "smaller"
else
  "greater or equal"
end

end

include InputMethod
end

Even the indent is added to fit the class definition. :slight_smile:

Pastie: Parked at Loopia
Code:

require ‘facets’
require ‘facets/core/kernel/singleton’

class Module
public :class_variable_set, :class_variable_get # public in 1.9 (?)
end

module InputMethod
def self.included(klass)
klass.class_variable_set(:@@inputmethods, methods=Hash.new{|h,k|
h[k]=[]})
include_point = caller.first
klass.singleton.send(:define_method,:patch!) {
return if methods.empty?
_, file, line = /(.*):(\d+)/.match(include_point).to_a
puts “Adding methods #{methods.keys.join(‘,’)} to #{klass}”
contents = File.readlines(file)

  ix = line.to_i-1
  indent = contents[ix][/^\s*/]
  code = methods.values.join("\n").split("\n").map{|l| indent + l

}.join(“\n”) # add indent
contents[ix] = code + “\n\n” + contents[ix] # insert methods
before include statement
File.open(file,‘w’) {|f| f << contents.join }
methods.clear
}
klass.singleton.send(:define_method,:patches) {
methods.values.join(“\n”) }
klass.singleton.send(:define_method,:remove_patch) {|m|
methods.delete m
remove_method m
}
end

def method_missing(method,*args)
print "Give the method definition for
#{self.class}##{method}\nEND to force input end, display error and
return nil\n> ";
method_body = line = gets
begin
self.class.class_eval(method_body)
rescue SyntaxError
return puts(“Syntax error: #{$!.message[/[^:]+\Z/].lstrip}”) if
line.chomp == ‘END’
print '> ’
method_body << line=gets
retry
end
self.class.class_variable_get(:@@inputmethods)[method] = method_body
send(method,*args)
end
end

I’ve been away from e-mail for a few days. I haven’t looked at anyone
elses solution yet.

Here’s my initial solution which didn’t take long. I expect to refine
this with more function in the next few days.

I split out the function into a separate class
MethodPrompter::Interactor, so as not to polute the “class under
training” with extra methods while allowing for modular extension of
the function.

=== method_prompter.rb ===
module MethodPrompter

    class Interactor

            def initialize(target_object)
                    @target_object = target_object
            end

            def prompt_for_method(symbol, *args)
                    puts "#{symbol} is undefined"
                    puts "Please define what I should do (end with

a newline):"
@method_body = []
print ">> "
while line = gets
break if line == “\n”
puts “#{line.empty?} >>#{line}<<”
@method_body << line
print ">> "
end
end

            def parms_from(*args)
                    ""
            end

            def make_method(symbol, *args)
                    method_string = "def #{symbol.to_s}

#{parms_from(args)}\n" <<
@method_body.join(“\n”) <<
‘end’
@target_object.class.module_eval(method_string,
‘line’,
-1)
end
end

    def method_missing(symbol, *args)
            interactor = Interactor.new(self)
            interactor.prompt_for_method(symbol)
            interactor.make_method(symbol, args)
    end

end


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

Not as nice as Tim Hollingsworth’s use of the method signature, but
oh well.

-Brent

Example Use

irb(main):001:0> include Friendly
=> Object
irb(main):002:0> foo = bar * z
It appears that bar is undefined.
Please define what I should do (end with a blankline):
12

It appears that z is undefined.
Please define what I should do (end with a blankline):
4

=> 48
irb(main):003:0> foo
=> 48
irb(main):004:0> bar
=> 12
irb(main):005:0> z
=> 4
irb(main):006:0> added_methods
=> [:z, :bar]
irb(main):007:0> puts added_method_definitions.join("\n\n")
def z
4
end

def bar
12
end
=> nil
irb(main):008:0>

Solution

module Friendly

def method_missing name
@_new_methods ||= Hash.new
unless @_new_methods.has_key? name
prompt_for_definition name
end
eval @_new_methods[name]
end

def prompt_for_definition name
puts “It appears that #{name} is undefined.”
puts “Please define what I should do (end with a blankline):”
@_new_methods[name] = “”
while $stdin.gets !~ /^\s*$/
@new_methods[name] << $
end
end

def added_methods
@_new_methods.keys
end

def added_method_definitions
@_new_methods.map {|k,v|
s = “def #{k}\n "
v.rstrip!
s << v.gsub(”\n", "\n ")
s << “\nend”
}
end

end

My solution intercepts the missing method, generates the method
signature (including named arguments) and spits it back at the user.
The user fills in the body and ends with an ‘end’.

The method is built as a string and simply eval’d against the
instance, producing a singleton method. The method string is also
stored in an array for later printing.

It uses the class of the given parameters as names for the method
arguments. This works up to the point where you have multiple
arguments of the same type.

example:
irb(main):004:0> o.foobar(“hello”, 3)
def foobar(string, fixnum)
fixnum.times {puts string}
end
=> nil
irb(main):005:0> o.foobar(“hello”, 3)
hello
hello
hello
=> 3
irb(main):006:0> o.moobar(“goodbye”)
def moobar(string)
puts string.upcase
end
=> nil
irb(main):007:0> o.moobar(“goodbye”)
GOODBYE
=> nil
irb(main):008:0> o.qaprint
def foobar(string, fixnum)
fixnum.times {puts string}
end

def moobar(string)
puts string.upcase
end
=> nil
irb(main):009:0>

code:

module QAPrototype

def method_missing(meth, *args)
arg_list = args.map {|arg| arg.class.to_s.downcase}.join(", ")
meth_def = “def #{meth}(#{arg_list})\n”
puts meth_def
while (line = gets) != “end\n”
meth_def += " " + line
end
meth_def += “end\n”

 eval meth_def

 @qamethods ||= []
 @qamethods << meth_def
 nil

end

def qaprint
puts @qamethods.join("\n") if @qamethods
end
end

quoth the Matt T.:

@Darren:

Actually, you can declare an attr in the module and it will work as an
instance variable in the class it’s mixed in. For example:

module QAPrototype
attr :_methods_added
end

That’s what I did.

Thanks for the tip. I was reading the section on mixins in the pickaxe
book,
and tried doing it with an instance variable, but the code examples led
me to
believe that the attribute could only be set/read within the including
class
itself. Coupled with the fact that I was getting “No method ‘[]’ for
Nil:Nilclass” (which I gather now was unrelated) I settled on the global
solution…

Also, print does work with one liners, but you need to flush out the
buffer each time you print the one line with
$stdout.flush. (Thanks to to cool folks in #ruby-lang for
that info.)

What I mean here is that after I split the string which describes the
method
to an array for pretty printing my subscripting only works if the method
definition is 2+ lines of code (the ‘def foo’ and ‘end’ lines are
freebies),
to make a total of 4+…

An artificial limitation I know, I will fix it when I add consideration
for
method args. After reading a couple other solutions it seems much easier
to
add this than I had imagined.

As for #ruby-lang, I have to agree. Someone in there saved my bacon
too…
Coming from more of a Python background, my first instinct to tackle
this
problem was to overload/redefine the “NoMethodError” exception to get it
to
run my code. This, of course, didn’t get me far, and I was directed
to “method_missing”.

I guess I should just read the built in/standard lib docs strait through
so I
will get a sense of what all methods are available…

-d

Here’s solution 2…

It now handles args, though somewhat poorly. If you know what you are
doing
you can get around this but it will win no points for user-friendlyness.
Also
got rid of the globals, I had to put @imethods within ‘method_missing’
itself, whether or not I used :attr_accessor

I was wrong about not printing one-liners, as I have found
[1…array.size-2]
will return the middle item of a three item array, so it works just
fine.
Ruby is smart(er than me)…

Here is irb session showing usage:
############################

load ‘quiz91.rb’
class Test; include MethodMaker; end
test = Test.new
=> #Test:0xa7ae1f80
test.print_address(:number, :street)
No such method: print_address
Care to define print_address? (y/n) y
Enter method definition ([ctrl-d] when done):
puts “#{number} #{street}”
=> nil
irb> test.print_address(“Four”, “Pennsylvania Plaza”)
Four Pennsylvania Plaza
=> nil
test.print_address(350, “Fifth Avenue”)
350 Fifth Avenue
=> nil
test.print_name(“Joe”, “Ruby”)
No such method: print_name
Care to define print_name? (y/n) n

bail out, or else ‘Joe’ and ‘Ruby’ will be the arg names

NoMethodError: print_name
from ./quiz91.rb:14:in `method_missing’
from (irb):10

test.print_name(:first,:last)
No such method: print_name
Care to define print_name? (y/n) y
Enter method definition ([ctrl-d] when done):
puts “#{first} #{last}”
=> nil
test.print_name(“Joe”,“Ruby”)
Joe R.

Code:

module MethodMaker

def method_missing(method_name, *args)
@imethods = {}
puts “No such method: #{method_name}”
# It might be a simple typo…
# so give a chance to bail out.
print "Care to define #{method_name}? (y/n) "
if $stdin.gets.chomp == “y” # ‘y’
prompt_method(method_name, args)
else
raise NoMethodError, “#{method_name}”
end
end

def prompt_method(name, args=nil)
puts “Enter method definition ([ctrl-d] when done):”
meth = “def #{name}(#{args ? args.join(”, “) : “”})”
while $stdin.gets
meth += $_
end
meth += “end”
meth = meth.gsub("\n",";")
@imethods["#{name}"] = meth
eval meth
end

def print_method(name)
meth_Array = @imethods[name].split(";")
puts meth_Array[0]
meth_Array[1…meth_Array.size-2].each { |line| puts " #{line}" }
puts meth_Array[-1]
end
end
#################################
-d

Here’s a simple IRB session using mine:

$ irb -rqap

Call #start if you want to get a head start on QAPrototype…

irb(main):001:0> start

irb(prep):001:0> @f = Foo.new
irb(prep):002:0> @f.bar

For your convenience in testing, I’ve created a class called
Foo and have already mixed in QAPrototype! Aren’t you glad?
And while I was at it, I went ahead and created an instance
of Foo and put it into @f. Now we can all shout for joy!
Heck, we even started up the conversation with method_missing!

bar is undefined.
Please define what I should do, starting with arguments
this method should accept (skip and end with newline):

def bar
“Hello!”

end

Okay, I got it.

Calling the method now!

=> “Hello!”
irb(main):002:0> @f.add 10, 12

add is undefined.
Please define what I should do, starting with arguments
this method should accept (skip and end with newline):

def add a, b
a - b

end

Okay, I got it.

Calling the method now!

=> -2
irb(main):003:0> @f.undo

add is now gone from this class.

=> nil
irb(main):004:0> @f.add 10, 12

add is undefined.
Please define what I should do, starting with arguments
this method should accept (skip and end with newline):

def add a, b
a + b

end

Okay, I got it.

Calling the method now!

=> 22
irb(main):005:0> @f.dump
class Foo
def bar
“Hello!”
end
def add a, b
a + b
end
end
=> nil
irb(main):006:0> @f.dump “/tmp/foo.rb”

Class was written to /tmp/foo.rb successfully!

=> nil
irb(main):007:0> exit
$

There you have it. Check out the file attached for my solution.

Of particular note to my solution is an #undo method that will remove
the last method added. As you can tell, it supports arguments.
However, I was not able to fully coax blocks to work properly, at
least on creation (when it calls it after you’ve created it). That’s
negligible, though, and I’ll probably iron that out a bit.

Cheers,

M.T.

P.S. - No, I didn’t go into another IRB session for those two lines…
I just printed that out to give the general idea of what just happened
to get things rolling.