[QUIZ] Method Auto Completion (#110)

The three rules of Ruby Quiz:

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 Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

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

···

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

by Robert Dober

Command Line Interfaces very often support command abbreviations The purpose of
this quiz is to automatically dispatch to methods on unambiguous abbreviations,
ask the user for clarification in case of ambiguous abbreviations and raise a
NoMethodError in case of an abbreviation that cannot be matched to any command.

Behavior of other methods defined in a class shall not be altered.

Be creative about the interface and about behavior. I have OTOH defined a small
test suite that makes assumptions about the interface and behavior. But the test
suite is only there for your convenience.

What is said below applies to the test suite and shall in no way inhibit any
alternative ideas.

  class Mine
    abbrev :step, :next, :stop
    abbrev :exit
  end
  
  Mine.new.e # should resolve to exit
  Mine.new.st # should prompt the user
  Mine.new.a # should still raise a NoMethodError

Abbreviation targets themselves are not expanded.

  class Nine
    abbrev :hash
    abbrev :has
  end
  
  Nine.new.ha # => [:hash, :has]
  Nine.new.has # => NoMethodError
  
  class Nine
    def has; 42; end
  end
  Nine.new.has # => 42

In order to allow for automated testing the test code shall not prompt the user
in case of an ambiguous abbreviation but return an array containing all (and
only all) possible completions as symbols. Note that the test suite sets the
global variable $TESTING to a true value for your convenience.

  http://rubyquiz.com/test-abbrev.rb

Last week I was kind of embarrassed that Bob chose to feature my late,
poorly commented, somewhat opaque solution, but I certainly appreciate
the inclusive spirit of Ruby Quiz. Here's a more timely submission for
the auto completion problem.

···

--------------------------------------------------------
class Object
  def self.abbrev(*args)
    module_eval <<-EOS
    @@abbrevs ||=
    @@abbrevs += args

    def method_missing(m)
      # abbrev targets themselves are not supposed to be expanded
      raise NoMethodError if @@abbrevs.include?(m)

      # which abbrev targets could match m, and which of those
correspond to methods?
      matches = @@abbrevs.select do |sym|
        sym.to_s.index(m.to_s) == 0 && methods.include?(sym.to_s)
      end

      case matches.size
        when 0
          raise NoMethodError
        when 1
          self.send(matches.first)
        else
          # multiple matches, pass them back to the user
          return matches if $TESTING
          puts matches.join(" ")
      end
    end
    EOS

  end
end
--------------------------------------------------------

This code passes the supplied tests, and I also added another test
when an earlier version of my code was doing things it shouldn't have:

--------------------------------------------------------
# check for unintended effects on other classes
class Test3 < Test::Unit::TestCase
  def setup
    @foo_class = Class.new {
      abbrev :start
      def start; nil end
    }
    @bar_class = Class.new {
      def start; nil end
    }
  end

  def test1
    f = @foo_class.new
    b = @bar_class.new
    assert_raise NoMethodError do
       b.send(:sta)
    end
  end
end
--------------------------------------------------------

I don't feel very confident in the approach I've taken, so I'm eager
to hear critique and see other solutions.

Krishna

On 1/19/07, Ruby Quiz <james@grayproductions.net> wrote:

The three rules of Ruby Quiz:

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 Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

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

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

by Robert Dober

Command Line Interfaces very often support command abbreviations The purpose of
this quiz is to automatically dispatch to methods on unambiguous abbreviations,
ask the user for clarification in case of ambiguous abbreviations and raise a
NoMethodError in case of an abbreviation that cannot be matched to any command.

Behavior of other methods defined in a class shall not be altered.

Be creative about the interface and about behavior. I have OTOH defined a small
test suite that makes assumptions about the interface and behavior. But the test
suite is only there for your convenience.

What is said below applies to the test suite and shall in no way inhibit any
alternative ideas.

        class Mine
          abbrev :step, :next, :stop
          abbrev :exit
        end

        Mine.new.e # should resolve to exit
        Mine.new.st # should prompt the user
        Mine.new.a # should still raise a NoMethodError

Abbreviation targets themselves are not expanded.

        class Nine
          abbrev :hash
          abbrev :has
        end

        Nine.new.ha # => [:hash, :has]
        Nine.new.has # => NoMethodError

        class Nine
          def has; 42; end
        end
        Nine.new.has # => 42

In order to allow for automated testing the test code shall not prompt the user
in case of an ambiguous abbreviation but return an array containing all (and
only all) possible completions as symbols. Note that the test suite sets the
global variable $TESTING to a true value for your convenience.

        http://rubyquiz.com/test-abbrev.rb

Returning an array when the answer is ambiguous is a very bad way to do
this. Instead, I throw an exception (you can get the candidates from the
#candidates attribute of the exception).

I also saw no reason to specifically name the methods that get abbreviated
-- rather, abbreviation works on all methods in the object and its super
classes.

require 'abbrev'

class AmbiguousExpansionError < StandardError
   attr_accessor :candidates
   def initialize(name,possible_methods)
      super("Ambiguous abbreviaton: #{name}\n"+
            "Candidates: #{possible_methods.join(", ")}")
      @candidates=possible_methods
   end
end

module Abbreviator
   def method_missing name,*args
      abbrevs=methods.abbrev
      return send(abbrevs[name.to_s],*args) if abbrevs[name.to_s]
      meths=abbrevs.reject{|key,value| key!~/^#{name}/}.values.uniq
      raise AmbiguousExpansionError.new(name, meths) if meths.length>1
      return super(name,*args)
   end
end

···

On Fri, 19 Jan 2007 22:45:05 +0900, Ruby Quiz wrote:

The three rules of Ruby Quiz:

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 Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

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

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

by Robert Dober

Command Line Interfaces very often support command abbreviations The purpose of
this quiz is to automatically dispatch to methods on unambiguous abbreviations,
ask the user for clarification in case of ambiguous abbreviations and raise a
NoMethodError in case of an abbreviation that cannot be matched to any command.

Behavior of other methods defined in a class shall not be altered.

Be creative about the interface and about behavior. I have OTOH defined a small
test suite that makes assumptions about the interface and behavior. But the test
suite is only there for your convenience.

What is said below applies to the test suite and shall in no way inhibit any
alternative ideas.

  class Mine
    abbrev :step, :next, :stop
    abbrev :exit
  end
  
  Mine.new.e # should resolve to exit
  Mine.new.st # should prompt the user
  Mine.new.a # should still raise a NoMethodError

Abbreviation targets themselves are not expanded.

  class Nine
    abbrev :hash
    abbrev :has
  end
  
  Nine.new.ha # => [:hash, :has]
  Nine.new.has # => NoMethodError
  
  class Nine
    def has; 42; end
  end
  Nine.new.has # => 42

In order to allow for automated testing the test code shall not prompt the user
in case of an ambiguous abbreviation but return an array containing all (and
only all) possible completions as symbols. Note that the test suite sets the
global variable $TESTING to a true value for your convenience.

  http://rubyquiz.com/test-abbrev.rb

--
Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
Department of Computer Science. Illinois Institute of Technology.
http://www.iit.edu/~kbloom1/

Here is my solution.
It does not pass the supplied tests since it uses a different interface when
there are multiple matches : It a raises a MultipleMethods exception with an
array of the possible matches.

There is an additional abbrev method in Module which defines the methods to be
abbreviated. If used with the single argument 'true' it enables abbreviations
for all methods of the object.
the 'abbreviated_methods' private method of Object does the intersection of
the defined object class's abbreviated methods and the current actual methods
of the object.
Then the abbreviation are caught with method_missing.

quiz110.rb :

class MultipleMethods < NoMethodError
  attr_reader :methods
  def initialize(methods)
    @methods = methods
    super("Multiple Choices : " + @methods.join(' '))
  end
end

class Module
  attr_reader :abbreviated_methods

  private
  # Defines a list of methods to be abbreviated.
  # If auto = true, then all the methods are abbreviated

···

#
  # class Test
  # abbrev # All the methods of Test will be abbreviated
  # end
  #
  # class Test2
  # abbrev :first, :last # Only the methods first and last may be
abbreviated
  # end
  #
  # If multiple choices are possible, a MultipleMethods exception is raised
which contains a list of the matches.
  def abbrev(auto = true, *args)
    if auto.respond_to? :to_sym
      @abbreviated_methods ||= []
      @abbreviated_methods += args.collect {|m| m.to_sym }.unshift auto.to_sym
    elsif args.empty?
      auto ? @abbreviated_methods = [] : @abbreviated_methods = nil
    else
      raise ArgumentError
    end
  end
end

class Object
  alias :abbrev_method_missing :method_missing
  def method_missing(sym, *args)
    found = abbreviated_methods.select { |m| m.to_s =~ /^#{sym}/ }
    if found.empty?
      abbrev_method_missing(sym, *args)
    elsif found.size == 1
      send found.first, *args
    else
      raise MultipleMethods.new(found)
    end
  end

  private
  def abbreviated_methods
    if self.class.abbreviated_methods.nil?
      []
    elsif self.class.abbreviated_methods.empty?
      methods
    else
      self.class.abbreviated_methods & methods.collect {|m| m.to_sym}
    end
  end
end

My Solution doesn't pass all tests, since i think that when a method
:aa is defined it should offer completion from :a to :aa (even when
the method doesn't exist in the first place but is defined later on)

http://pastie.caboo.se/34631

It's nothing fancy, just a quick 15 minutes-hack, was fun though :slight_smile:

^manveru

···

On 1/19/07, Ruby Quiz <james@grayproductions.net> wrote:

The three rules of Ruby Quiz:

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 Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

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

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

by Robert Dober

Command Line Interfaces very often support command abbreviations The purpose of
this quiz is to automatically dispatch to methods on unambiguous abbreviations,
ask the user for clarification in case of ambiguous abbreviations and raise a
NoMethodError in case of an abbreviation that cannot be matched to any command.

Behavior of other methods defined in a class shall not be altered.

Be creative about the interface and about behavior. I have OTOH defined a small
test suite that makes assumptions about the interface and behavior. But the test
suite is only there for your convenience.

What is said below applies to the test suite and shall in no way inhibit any
alternative ideas.

        class Mine
          abbrev :step, :next, :stop
          abbrev :exit
        end

        Mine.new.e # should resolve to exit
        Mine.new.st # should prompt the user
        Mine.new.a # should still raise a NoMethodError

Abbreviation targets themselves are not expanded.

        class Nine
          abbrev :hash
          abbrev :has
        end

        Nine.new.ha # => [:hash, :has]
        Nine.new.has # => NoMethodError

        class Nine
          def has; 42; end
        end
        Nine.new.has # => 42

In order to allow for automated testing the test code shall not prompt the user
in case of an ambiguous abbreviation but return an array containing all (and
only all) possible completions as symbols. Note that the test suite sets the
global variable $TESTING to a true value for your convenience.

        http://rubyquiz.com/test-abbrev.rb

This is my solution. Probably my fastest and shortest ruby quiz solution, but no less fun! Props to the creator of this quiz.

In technicolor glory: http://pastie.caboo.se/34720
Also attached. And pasted below.

class Object
  def method_missing(method, *args, &blk)
    # Gather all possible methods it could be.
    possibleMethods = self.methods.select {|x| x =~ /^#{Regexp.escape(method.to_s)}/ }
    
    case possibleMethods.size
    # No matching method.
    when 0
      raise NoMethodError.new("undefined method `#{method}' for #{self.inspect}:#{self.class.name}")
    
    # One matching method, call it.
    when 1
      method = possibleMethods.first
      self.send(method, *args, &blk)
    
    # Multiple possibilities, return an array of the possibilities.
    else
      possibleMethods
    end
  end
end

Ruby Quiz wrote:

method_completion.rb (569 Bytes)

···

The three rules of Ruby Quiz:

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 Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

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

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

by Robert Dober

Command Line Interfaces very often support command abbreviations The purpose of
this quiz is to automatically dispatch to methods on unambiguous abbreviations,
ask the user for clarification in case of ambiguous abbreviations and raise a
NoMethodError in case of an abbreviation that cannot be matched to any command.

Behavior of other methods defined in a class shall not be altered.

Be creative about the interface and about behavior. I have OTOH defined a small
test suite that makes assumptions about the interface and behavior. But the test
suite is only there for your convenience.

What is said below applies to the test suite and shall in no way inhibit any
alternative ideas.

  class Mine
    abbrev :step, :next, :stop
    abbrev :exit
  end
  
  Mine.new.e # should resolve to exit
  Mine.new.st # should prompt the user Mine.new.a # should still raise a NoMethodError

Abbreviation targets themselves are not expanded.

  class Nine
    abbrev :hash
    abbrev :has
  end
  
  Nine.new.ha # => [:hash, :has] Nine.new.has # => NoMethodError
  
  class Nine
    def has; 42; end
  end
  Nine.new.has # => 42

In order to allow for automated testing the test code shall not prompt the user
in case of an ambiguous abbreviation but return an array containing all (and
only all) possible completions as symbols. Note that the test suite sets the
global variable $TESTING to a true value for your convenience.

  http://rubyquiz.com/test-abbrev.rb

My Solution doesn't pass all tests, since i think that when a method
:aa is defined it should offer completion from :a to :aa (even when
the method doesn't exist in the first place but is defined later on)

http://pastie.caboo.se/34631

http://pastie.caboo.se/34632 - forgot to remove the other stuff :expressionless: and
somehow it didn't set highlighting to ruby...

···

It's nothing fancy, just a quick 15 minutes-hack, was fun though :slight_smile:

^manveru