[RCR] abstract method in Ruby

To define abstract method in Ruby, I use NotImplementedError like the
following:

···

----------
  class Foo
    def m1
      raise NotImpelemntedError("#{self.class.name}#m1() is not
implemented.")
    end
  end
  ----------

I think it is convenient if the Module#abstract_method is defined.

  ----------
  class Module
    def abstract_method(*method_names)
      method_names.each do |name|
        s = <<-END
          def #{name}
            mesg = "\#{self.class.name}##{name}() is not implemented."
            raise NotImplementedError.new(mesg)
          end
        END
        module_eval s
    end
  end

  class Foo
    abstract_method :m1, :m2, :m3 # define abstract methods
  end

  obj = Foo.new
  obj.m1 #=> Foo#m1() is not implemented yet. (NotImplementedError)
  ----------

But this solution doesn't allow us to define a method with arguments.
The following is another solution to define abstract method
which is able to define a method with arguments.

  ? example.rb
  ----------
  module Abstract # or Module?
    def not_implemented # or should_be_implemented?
      backtrace = caller()
      method_name = (backtrace.shift =~ /`(\w+)'$/) && $1
      mesg = "#{self.class.name}##{method_name}() is not implemented."
      err = NotImplementedError.new(mesg)
      err.set_backtrace(backtrace)
      raise err
    end
  end

  class Foo
    include Abstract
    def m1(arg)
      not_implemented
    end
  end

  obj = Foo.new
  p obj.m1('abc') #=> example.rb:20: Foo#m1() is not implemented.
(NotImplementedError)
  ----------

I think this is useful for everyone and I hope it will be included in
Ruby.
Could you give me any advices?

--
regards,
kwatch

In the last four years of using Ruby, I thought I needed abstract
methods in the first few months. Then I learned what value Modules as
mixins gave me.

I do not believe that this is a common enough need that it needs to be
in the core of Ruby.

I encourage you to do what others have done with this sort of
mismatched feature and make a library that implements it and make it
available for use. Your pure-Ruby implementation looks more than
sufficient.

More than that, though, I encourage you to rethink why you need
abstract methods. Most of the time this is because you're thinking in
terms of C++ or Java inheritance, when Ruby's mixins are both more
powerful and applicable in most cases where you would define a
hierarchy that has abstract methods.

-austin

···

On 3/11/06, kwatch <kwa@kuwata-lab.com> wrote:

To define abstract method in Ruby, I use NotImplementedError like the
following:

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

I think it is convenient if the Module#abstract_method is defined.

I'm of a similar opinion with others that predeclaring an abstract
method that raises NotImplementedError is of limited use when compared
with the existing NoMethodError duck-typing approach. As such, I don't
think it belongs in the core.

*However*, this is a useful concept which could be used to extend the
capabilities of the language for those who do still want it. I
encourage you to build a library from this idea and publicize it. Just
because it doesn't belong in the core doesn't mean it won't be useful
to some. One place I can see this being used is if the
NotImplementedError gave a more descriptive output, such as "#{self}
requires that the host implement the method '#{method_name}'." E.g.
"Enumerable requires that the host implement the method 'each'."

  ----------
  class Module
    def abstract_method(*method_names)
      method_names.each do |name|
        s = <<-END
          def #{name}
            mesg = "\#{self.class.name}##{name}() is not implemented."
            raise NotImplementedError.new(mesg)
          end
        END
        module_eval s
    end
  end

  class Foo
    abstract_method :m1, :m2, :m3 # define abstract methods
  end

  obj = Foo.new
  obj.m1 #=> Foo#m1() is not implemented yet. (NotImplementedError)
  ----------

But this solution doesn't allow us to define a method with arguments.

One thing that you can do to make this approach (which seems cleaner
and simpler than the backtrace manipulation approach you later
proposed) more flexible by removing the arity restriction on the
method using the splat operator:

  $ cat abstract.rb
  class Module
    def abstract_method(*method_names)
      mesg_template = "#{self} requires that the host implement the
method '%s'."
      method_names.each do |name|
        mesg = mesg_template % [name]
        module_eval <<-END
          def #{name}(*args)
            raise NotImplementedError.new("#{mesg}")
          end
        END
      end
    end
  end

  module MyModule
    def foo
      bar("test")
    end

    abstract_method :bar
  end

  class MyClass
    include MyModule
  end

  a = MyClass.new
  b = MyClass.new

  class << a
    def bar( s )
      puts s
    end
  end

  a.foo
  b.foo

  $ ruby abstract.rb
  test
  (eval):2:in `bar': MyModule requires that the host implement the
method 'bar'. (NotImplementedError)
          from abstract.rb:17:in `foo'
          from abstract.rb:37

···

On 3/11/06, kwatch <kwa@kuwata-lab.com> wrote:

--
Jacob Fugal

To define abstract method in Ruby, I use NotImplementedError
like the following:

You can't call them *abstract* methods if you implement them
like this.

In Java, the presence of methods is checked at compiletime, if
they are defined "abstract". Your solution only checks the
presence of these methods at runtime, which is already checked
by Ruby itself.

So, what functionality or checks are you really adding?

gegroet,
Erik V. - http://www.erikveen.dds.nl/

Thanks Austin,

Austin Ziegler wrote:

In the last four years of using Ruby, I thought I needed abstract
methods in the first few months. Then I learned what value Modules as
mixins gave me.

I do not believe that this is a common enough need that it needs to be
in the core of Ruby.

Well, I think that abstract method is more basic than 'singleton.rb'
or 'delegate.rb' which are bundled in Ruby.

I encourage you to do what others have done with this sort of
mismatched feature and make a library that implements it and make it
available for use. Your pure-Ruby implementation looks more than
sufficient.

I'll make it library and register it to RubyForge.

More than that, though, I encourage you to rethink why you need
abstract methods. Most of the time this is because you're thinking in
terms of C++ or Java inheritance, when Ruby's mixins are both more
powerful and applicable in most cases where you would define a
hierarchy that has abstract methods.

In my opinion, 'mixin' is one thing and 'abstract method' is another.
Mixin doesn't cover abstract method.

The following is an example of visitor pattern.
It shows that mixin doesn't cover abstract method.

···

----------
module Visitor
  def visit_foo(acceptor) # abstract method
    mesg = "#{self.class.name}#visit_foo() is not implemented."
    raise NotImplementedError.new(mesg)
  end

  def visit_bar(acceptor) # abstract method
    mesg = "#{self.class.name}#visit_foo() is not implemented."
    raise NotImplementedError.new(mesg)
  end
end

class MyVisitor
  include Visitor # mix-in

  def visit_foo(acceptor)
    puts "visit_foo() called."
  end

  def visit_bar(acceptor)
    puts "visit_bar() called."
  end
end

class Foo
  def accept(visitor)
    visitor.visit_foo(self)
  end
end

class Bar
  def accept(visitor)
    visitor.visit_bar(self)
  end
end
----------

Ruby's mix-in is more sophisticated solution than interface of Java
or multiple inheritance of C++, I think.
But mix-in and abstract method are different thing.

--
regards,
kwatch

Jacob Fugal wrote:

I'm of a similar opinion with others that predeclaring an abstract
method that raises NotImplementedError is of limited use when compared
with the existing NoMethodError duck-typing approach. As such, I don't
think it belongs in the core.

Hmm...
IMO, abstract method is more useful than 'singleton.rb' or
'delegate.rb'.
It's just my opinion.

*However*, this is a useful concept which could be used to extend the
capabilities of the language for those who do still want it. I
encourage you to build a library from this idea and publicize it. Just
because it doesn't belong in the core doesn't mean it won't be useful
to some.

Yes, I'm going to release 'abstract.rb'.

One place I can see this being used is if the
NotImplementedError gave a more descriptive output, such as "#{self}
requires that the host implement the method '#{method_name}'." E.g.
"Enumerable requires that the host implement the method 'each'."

....(snip)...

One thing that you can do to make this approach (which seems cleaner
and simpler than the backtrace manipulation approach you later
proposed) more flexible by removing the arity restriction on the
method using the splat operator:

Thaks for your good advice.
I changed Module#abstract_method() like the following:

···

----------
  class Module
    def abstract_method args_str, *method_names
      method_names.each do |name|
        mesg = "class %s should implement abstract method
`#{self.name}##{name}()'."
        module_eval <<-END
          def #{name}(#{args_str})
            err = NotImplementedError.new(mesg % self.class.name)
            err.set_backtrace caller()
            raise err
          end
        END
      end
    end
  end
  ----------

Manipulating backtrace of exception is need in order to show
linenumber in which an abstract method is invoked.

example:

  ----------
  require 'abstract'
  class Foo
    abstract_method '*args', :m1, :m2 # removing the arity
restriction
  end
  class Bar < Foo
    def m1; puts "Foo#m1() called."; end
  end
  obj = Bar.new
  obj.m1 #=> "Foo#m1() called."
  obj.m2 #=> ex.rb:10: class Bar should implement abstract method
`Foo#m2()'. (NotImplementedError)
  ----------

--
regards,
kwatch

Jacob Fugal wrote:

On 3/11/06, kwatch <kwa@kuwata-lab.com> wrote:
> I think it is convenient if the Module#abstract_method is defined.

I'm of a similar opinion with others that predeclaring an abstract
method that raises NotImplementedError is of limited use when compared
with the existing NoMethodError duck-typing approach. As such, I don't
think it belongs in the core.

*However*, this is a useful concept which could be used to extend the
capabilities of the language for those who do still want it. I
encourage you to build a library from this idea and publicize it. Just
because it doesn't belong in the core doesn't mean it won't be useful
to some. One place I can see this being used is if the
NotImplementedError gave a more descriptive output, such as "#{self}
requires that the host implement the method '#{method_name}'." E.g.
"Enumerable requires that the host implement the method 'each'."

> ----------
> class Module
> def abstract_method(*method_names)
> method_names.each do |name|
> s = <<-END
> def #{name}
> mesg = "\#{self.class.name}##{name}() is not implemented."
> raise NotImplementedError.new(mesg)
> end
> END
> module_eval s
> end
> end
>
> class Foo
> abstract_method :m1, :m2, :m3 # define abstract methods
> end
>
> obj = Foo.new
> obj.m1 #=> Foo#m1() is not implemented yet. (NotImplementedError)
> ----------
>
> But this solution doesn't allow us to define a method with arguments.

One thing that you can do to make this approach (which seems cleaner
and simpler than the backtrace manipulation approach you later
proposed) more flexible by removing the arity restriction on the
method using the splat operator:

  $ cat abstract.rb
  class Module
    def abstract_method(*method_names)
      mesg_template = "#{self} requires that the host implement the
method '%s'."
      method_names.each do |name|
        mesg = mesg_template % [name]
        module_eval <<-END
          def #{name}(*args)
            raise NotImplementedError.new("#{mesg}")
          end
        END
      end
    end
  end

  module MyModule
    def foo
      bar("test")
    end

    abstract_method :bar
  end

  class MyClass
    include MyModule
  end

  a = MyClass.new
  b = MyClass.new

  class << a
    def bar( s )
      puts s
    end
  end

  a.foo
  b.foo

  $ ruby abstract.rb
  test
  (eval):2:in `bar': MyModule requires that the host implement the
method 'bar'. (NotImplementedError)
          from abstract.rb:17:in `foo'
          from abstract.rb:37

--
Jacob Fugal

In Java, the presence of methods is checked at compiletime,
if they are defined "abstract". Your solution only checks the
presence of these methods at runtime, which is already
checked by Ruby itself.

In this case (class definitions in Ruby), the closest to
"compile time" is probably "allocate time". I mean, the first
time you use a class after you defined it, is the moment you
create an instance of that class. (Other ideas?)

If you want to check a class for specific methods, "allocate
time" might be the right time to do so. (Unless you're willing
to wait until you run into the traditional NoMethodError.) I
came up with the following, using wrap_module_method (as
discussed before [1] and summarized here [2]) to wrap
Class#new.

This is probably not a good solution, unless you don't care
about performance... It might be optimized by unwrapping the
Class#new once the class has been checked. Although that's just
a theory, for now...

gegroet,
Erik V. - http://www.erikveen.dds.nl/

[1] http://tinyurl.com/ntbl4
[2] Ruby Monitor-Functions - Or Meta-Meta-Programming in Ruby

···

----------------------------------------------------------------

require "ev/metameta" # That's where the code from [2] lives...

class Class
   def abstract_method(*method_names)
     wrap_module_method(:new) do |org_method, args, block, obj|
       method_names.each do |method_name|
         obj.instance_eval do
           unless instance_methods.include?(method_name.to_s)
             raise NotImplementedError, "Class #{self} doesn't
implement method #{method_name}."
           end
         end
       end

       org_method.call(*args, &block)
     end
   end
end

----------------------------------------------------------------

class Foo
   abstract_method :baz
end

class Bar1 < Foo
end

class Bar2 < Foo
   def baz
     :ok
   end
end

[Bar1, Bar2].each do |klass|
   begin
     klass.new
   rescue NotImplementedError => e
     $stderr.puts e.message
   end
end

----------------------------------------------------------------

I think you're over engineering. Let's consider the Enumerable module for instance. It has an "abstract" method of sorts, each.

class A
    include Enumerable
end

a = A.new
a.map { |x| x + 1 }

Oh look, this code already raises an exception, a NoMethodError. Now I know that to include Enumerable I have to have an each method. That's all there is to it. Look at your Visitor example for instance, in ruby there's not even a need for the Visitor module, all you have to do is implement visit_foo and visit_bar in MyVisitor. In ruby, no one cares who your parents were, all they care about is if you know what you are talking about.

class MyVisitor
   def visit_foo(acceptor)
     puts "visit_foo() called."
   end

   def visit_bar(acceptor)
     puts "visit_bar() called."
   end
end

class Foo
    def accept(visitor)
          visitor.visit_foo(self)
    end
end

class Bar
     def accept(visitor)
          vistor.visit_bar(self)
    end
end

Bam! Same thing as your code, will still raise the same exceptions, but ONLY if you really need them. Start thinking like a duck man. Ask yourself what is the purpose of an abstract method. Then ask yourself if that need is already fufilled in ruby.

···

On Mar 11, 2006, at 5:23 PM, kwatch wrote:

Thanks Austin,

Austin Ziegler wrote:

In the last four years of using Ruby, I thought I needed abstract
methods in the first few months. Then I learned what value Modules as
mixins gave me.

I do not believe that this is a common enough need that it needs to be
in the core of Ruby.

Well, I think that abstract method is more basic than 'singleton.rb'
or 'delegate.rb' which are bundled in Ruby.

I encourage you to do what others have done with this sort of
mismatched feature and make a library that implements it and make it
available for use. Your pure-Ruby implementation looks more than
sufficient.

I'll make it library and register it to RubyForge.

More than that, though, I encourage you to rethink why you need
abstract methods. Most of the time this is because you're thinking in
terms of C++ or Java inheritance, when Ruby's mixins are both more
powerful and applicable in most cases where you would define a
hierarchy that has abstract methods.

In my opinion, 'mixin' is one thing and 'abstract method' is another.
Mixin doesn't cover abstract method.

The following is an example of visitor pattern.
It shows that mixin doesn't cover abstract method.

----------
module Visitor
  def visit_foo(acceptor) # abstract method
    mesg = "#{self.class.name}#visit_foo() is not implemented."
    raise NotImplementedError.new(mesg)
  end

  def visit_bar(acceptor) # abstract method
    mesg = "#{self.class.name}#visit_foo() is not implemented."
    raise NotImplementedError.new(mesg)
  end
end

class MyVisitor
  include Visitor # mix-in

  def visit_foo(acceptor)
    puts "visit_foo() called."
  end

  def visit_bar(acceptor)
    puts "visit_bar() called."
  end
end

class Foo
  def accept(visitor)
    visitor.visit_foo(self)
  end
end

class Bar
  def accept(visitor)
    visitor.visit_bar(self)
  end
end
----------

Ruby's mix-in is more sophisticated solution than interface of Java
or multiple inheritance of C++, I think.
But mix-in and abstract method are different thing.

--
regards,
kwatch

I released abstract.rb 1.0.0.
http://rubyforge.org/projects/abstract/

'abstract.rb' is a library which enable you to define abstract method
in Ruby.

The followings are examples:

  ## example1. (shorter notation)
  require 'rubygems' # if installed with 'gem install'
  require 'abstract'
  class Foo
    abstract_method 'arg1, arg2=""', :method1, :method2, :method3
  end

  ## example2. (RDoc friendly notation)
  require 'rubygems' # if installed with 'gem install'
  require 'abstract'
  class Bar
    # ... method1 description ...
    def method1(arg1, arg2="")
      not_implemented
    end
    # ... method2 description ...
    def method2(arg1, arg2="")
      not_implemented
    end
  end

Abstract method makes your code more descriptive.
It is useful even for dynamic language such as Ruby.

···

--
regards,
kwatch

how interesting!

this is a very appropriate for some circumstances which i'd never thought of -
i'm sure i'll steal it in the near future. :wink:

regards.

-a

···

On Mon, 13 Mar 2006, Erik Veenstra wrote:

In this case (class definitions in Ruby), the closest to "compile time" is
probably "allocate time". I mean, the first time you use a class after you
defined it, is the moment you create an instance of that class. (Other
ideas?)

--
share your knowledge. it's a way to achieve immortality.
- h.h. the 14th dali lama

A good portion of the GoF patterns rely on abstract methods (visitor,
abstract factory, template method, etc... ) which IMO only shine in
statically typed languages. It would be a shame to port all patterns
(and their required prerequisite: abstract methods) simply because they
are recognized in the static world and therefore would make a good fit
in Ruby.

The additional code needed for the ruby "abstract method" approach
doesn't make the code any more readable. The extra LoC actually slows
down the maintenance developer as she has to wade through more.

In both solutions, an exception is thrown at runtime. The abstract
method approach will not allow the developer to catch it any earlier
than the duck typed default.

The exception proposed although slightly more readable will not in
practice add any value as Ruby developers are used to seeing the
exception and can narrow in on it expediently.

We should simply rejoice in the power of the mixin and realize that the
abstract specifier was devised prior to (and probably due to the absence
of) this incredibly powerfull construct.

Lastly, the code becomes somewhat heavier which violates 2 key
principles of Ruby, one being the enjoyable to code principle, and the
other, least surprise principle.

ilan

···

--
Posted via http://www.ruby-forum.com/.

:slight_smile: True, yes. For documentation though, it's nice to know that you're supposed to implement something. You could point that out with a comment of course. But a comment wouldn't be picked up by some hypothetical parser that's looking at your code (not just to execute it) and trying to deduce things about it... :slight_smile: I think I like the idea of a declaration for an abstract methods because it makes clear your intention.

···

On 11 Mar 2006, at 22:58, Logan Capaldo wrote:

On Mar 11, 2006, at 5:23 PM, kwatch wrote:

Thanks Austin,

Austin Ziegler wrote:

In the last four years of using Ruby, I thought I needed abstract
methods in the first few months. Then I learned what value Modules as
mixins gave me.

I do not believe that this is a common enough need that it needs to be
in the core of Ruby.

Well, I think that abstract method is more basic than 'singleton.rb'
or 'delegate.rb' which are bundled in Ruby.

I encourage you to do what others have done with this sort of
mismatched feature and make a library that implements it and make it
available for use. Your pure-Ruby implementation looks more than
sufficient.

I'll make it library and register it to RubyForge.

More than that, though, I encourage you to rethink why you need
abstract methods. Most of the time this is because you're thinking in
terms of C++ or Java inheritance, when Ruby's mixins are both more
powerful and applicable in most cases where you would define a
hierarchy that has abstract methods.

In my opinion, 'mixin' is one thing and 'abstract method' is another.
Mixin doesn't cover abstract method.

The following is an example of visitor pattern.
It shows that mixin doesn't cover abstract method.

----------
module Visitor
  def visit_foo(acceptor) # abstract method
    mesg = "#{self.class.name}#visit_foo() is not implemented."
    raise NotImplementedError.new(mesg)
  end

  def visit_bar(acceptor) # abstract method
    mesg = "#{self.class.name}#visit_foo() is not implemented."
    raise NotImplementedError.new(mesg)
  end
end

class MyVisitor
  include Visitor # mix-in

  def visit_foo(acceptor)
    puts "visit_foo() called."
  end

  def visit_bar(acceptor)
    puts "visit_bar() called."
  end
end

class Foo
  def accept(visitor)
    visitor.visit_foo(self)
  end
end

class Bar
  def accept(visitor)
    visitor.visit_bar(self)
  end
end
----------

Ruby's mix-in is more sophisticated solution than interface of Java
or multiple inheritance of C++, I think.
But mix-in and abstract method are different thing.

--
regards,
kwatch

I think you're over engineering. Let's consider the Enumerable module for instance. It has an "abstract" method of sorts, each.

class A
   include Enumerable
end

a = A.new
a.map { |x| x + 1 }

Oh look, this code already raises an exception, a NoMethodError. Now I know that to include Enumerable I have to have an each method.

Logan,

Oh look, this code already raises an exception, a NoMethodError. Now
I know that to include Enumerable I have to have an each method.
That's all there is to it.

I think it is very important that the code itself is descriptive.
The following code doesn't describe that method each() is abstract.

  module Enumerable
    def map
      arr =
      each { |elem| arr << yield(elem) }
      arr
    end
  end

The following code itself describes that method each() is abstract.
It's more descriptive.

  module Enumerable
    abstract_method :each
    def map
      arr =
      each { |elem| arr << yield(elem) }
      arr
    end
  end

Please take care of reading code, as well as writing code.
You may insist that documentation is sufficient to describe that,
but documentation is assistant and code should be prime.

Oh look, this code already raises an exception, a NoMethodError.

NoMethodError is not proper to abstract method, I think.

Look at your Visitor example for instance,
in ruby there's not even a need for the Visitor module, all you have
to do is implement visit_foo and visit_bar in MyVisitor. In ruby, no
one cares who your parents were, all they care about is if you know
what you are talking about.

Assume that you need to define many visitor classes.

  module Visitor
    abstract_method :visit_foo, :visit_bar
  end

  class Hoge
    include Visitor
    def visit_foo; ... ; end
    def visit_bar; ... ; end
  end

  class Fuga
    include Visitor
    def visit_foo; ... ; end
    def visit_bar; ... ; end
  end

  class Geji
    include Visitor
    def visit_foo; ... ; end
    def visit_bar; ... ; end
  end

The above code itself describes that "class Hoge, Fuga, and Geji
are visotr classes" very clearly.
'Visitor' module may not be necessary when a number of visitor class
is only 1.
But there should be 'Visitor' module in the case that many visitor
classes are needed. It's more descriptive.

Start thinking like a duck man.

You mean 'duck typing'?
Duck typing is one thing and abstract method is another.

Ask yourself what is the purpose of an abstract method.
Then ask yourself if that need is already fufilled in ruby.

Abstract method is useful to realize 'descriptive code'.
It increases code maintanancability.

···

--
regards,
kwatch

Logan Capaldo wrote:

On Mar 11, 2006, at 5:23 PM, kwatch wrote:

> Thanks Austin,
>
> Austin Ziegler wrote:
>>
>> In the last four years of using Ruby, I thought I needed abstract
>> methods in the first few months. Then I learned what value Modules as
>> mixins gave me.
>>
>> I do not believe that this is a common enough need that it needs
>> to be
>> in the core of Ruby.
>
> Well, I think that abstract method is more basic than 'singleton.rb'
> or 'delegate.rb' which are bundled in Ruby.
>
>
>> I encourage you to do what others have done with this sort of
>> mismatched feature and make a library that implements it and make it
>> available for use. Your pure-Ruby implementation looks more than
>> sufficient.
>
> I'll make it library and register it to RubyForge.
>
>
>> More than that, though, I encourage you to rethink why you need
>> abstract methods. Most of the time this is because you're thinking in
>> terms of C++ or Java inheritance, when Ruby's mixins are both more
>> powerful and applicable in most cases where you would define a
>> hierarchy that has abstract methods.
>
> In my opinion, 'mixin' is one thing and 'abstract method' is another.
> Mixin doesn't cover abstract method.
>
> The following is an example of visitor pattern.
> It shows that mixin doesn't cover abstract method.
>
> ----------
> module Visitor
> def visit_foo(acceptor) # abstract method
> mesg = "#{self.class.name}#visit_foo() is not implemented."
> raise NotImplementedError.new(mesg)
> end
>
> def visit_bar(acceptor) # abstract method
> mesg = "#{self.class.name}#visit_foo() is not implemented."
> raise NotImplementedError.new(mesg)
> end
> end
>
> class MyVisitor
> include Visitor # mix-in
>
> def visit_foo(acceptor)
> puts "visit_foo() called."
> end
>
> def visit_bar(acceptor)
> puts "visit_bar() called."
> end
> end
>
> class Foo
> def accept(visitor)
> visitor.visit_foo(self)
> end
> end
>
> class Bar
> def accept(visitor)
> visitor.visit_bar(self)
> end
> end
> ----------
>
> Ruby's mix-in is more sophisticated solution than interface of Java
> or multiple inheritance of C++, I think.
> But mix-in and abstract method are different thing.
>
> --
> regards,
> kwatch
>
>

I think you're over engineering. Let's consider the Enumerable module
for instance. It has an "abstract" method of sorts, each.

class A
    include Enumerable
end

a = A.new
a.map { |x| x + 1 }

Oh look, this code already raises an exception, a NoMethodError. Now
I know that to include Enumerable I have to have an each method.
That's all there is to it. Look at your Visitor example for instance,
in ruby there's not even a need for the Visitor module, all you have
to do is implement visit_foo and visit_bar in MyVisitor. In ruby, no
one cares who your parents were, all they care about is if you know
what you are talking about.

class MyVisitor
   def visit_foo(acceptor)
     puts "visit_foo() called."
   end

   def visit_bar(acceptor)
     puts "visit_bar() called."
   end
end

class Foo
    def accept(visitor)
          visitor.visit_foo(self)
    end
end

class Bar
     def accept(visitor)
          vistor.visit_bar(self)
    end
end

Bam! Same thing as your code, will still raise the same exceptions,
but ONLY if you really need them. Start thinking like a duck man. Ask
yourself what is the purpose of an abstract method. Then ask yourself
if that need is already fufilled in ruby.

The exception proposed although slightly more readable will
not in practice add any value as Ruby developers are used to
seeing the exception and can narrow in on it expediently.

I'm not advocating these abstract methods in Ruby. On the
contrary, I don't like them either.

It's just that, when I read this thread, I thought: Can I do
that with my meta-meta-programming library? Answer: yes. That's
all what I wanted to know... :slight_smile:

It's not about "yes or no", but about "how if yes"...

gegroet,
Erik V. - http://www.erikveen.dds.nl/

i disagree strongly here. check out these two snippets that i am working on
as we speak:

     def will_process_holding *a, &b
       raise NotImplementedError
     end
     def process_holding *a, &b
       raise NotImplementedError
     end
     def will_process_incoming(*a, &b)
       raise NotImplementedError
     end
     def process_incoming(*a, &b)
       raise NotImplementedError
     end

vs

     abstract_method "will_process_holding"
     abstract_method "process_holding"
     abstract_method "will_process_incoming"
     abstract_method "process_incoming"

it's for precisely these kinds of simple repetetive defs that we have 'attr',
'attr_accessor', 'attr_writer', and 'attr_reader'. think about how simple all
these methods are to write - but people love the shorthand and the fact that
rdoc can grok something meaningful from the metaprogramming is quite
beneficial.

sure - i could just go full on duck type - but good luck reading the 2000 line
satellite image processing library that requires these four methods (and about
20 others) and trying to figure out which ones you need to implement. i nice
compact listing like this surely helps out with that task doesn't it?

here's another example from the standard lib

     tsort.rb
     ...

···

On Tue, 14 Mar 2006, ilan berci wrote:

A good portion of the GoF patterns rely on abstract methods (visitor,
abstract factory, template method, etc... ) which IMO only shine in
statically typed languages. It would be a shame to port all patterns (and
their required prerequisite: abstract methods) simply because they are
recognized in the static world and therefore would make a good fit in Ruby.

The additional code needed for the ruby "abstract method" approach doesn't
make the code any more readable. The extra LoC actually slows down the
maintenance developer as she has to wade through more.

       #
       # Should be implemented by a extended class.
       #
       # #tsort_each_node is used to iterate for all nodes over a graph.
       #
       def tsort_each_node # :yields: node
         raise NotImplementedError.new
       end

       #
       # Should be implemented by a extended class.
       #
       # #tsort_each_child is used to iterate for child nodes of _node_.
       #
       def tsort_each_child(node) # :yields: child
         raise NotImplementedError.new
       end
     ...

now, all this starts out at about line 230. this is a little deep is it not?
i think it'd be great of rdoc understood and abstract_method hook just like it
now supports the 'attr_XXX' hooks but, even without that, i'll take any hint i
can get in code like this.

ask yourself this: why did the author not just rely on duck-typing? why put
these methods into the code at all?

In both solutions, an exception is thrown at runtime. The abstract method
approach will not allow the developer to catch it any earlier than the duck
typed default.

unless, using one approach it takes an hour to figure out which methods to
impliment and the other way it does not. consider an abstract method

   module Interface
     def extra_debugging_info e
       ...
     end
   end

used like

   module Interface
     def foobar
       begin
         ...
       rescue ExceptionThatOccursOncePerMonth => e
         logger.warn{ extra_debugging_info e}
       end
     end
   end

how anoying! you really should have written that method because your code now
fails once per month. having an 'abstract_method' hook makes it so, even if
the developer didn't comment their code, the code itself clearly showed the
list of methods which should be implimented.

trust me - i realize this is really a small gain over pure duck typing - but
for the kinds of systems i'm writing, with 10,000 lines of ruby code, it's
really quite helpful to have this sort of note to myself jump out in the
code. that's why i've already written this:

   class Subscription
...
     def self::abstract_method m
       module_eval <<-definition
         def #{ m }(*a, &b)
           raise NotImplementedError
         end
       definition
     end
...

The exception proposed although slightly more readable will not in
practice add any value as Ruby developers are used to seeing the
exception and can narrow in on it expediently.

agreed. the point is to not get the exception in the first place becausee the
code was easier to understand how to hook into.

We should simply rejoice in the power of the mixin and realize that the
abstract specifier was devised prior to (and probably due to the absence
of) this incredibly powerfull construct.

Lastly, the code becomes somewhat heavier which violates 2 key
principles of Ruby, one being the enjoyable to code principle, and the
other, least surprise principle.

i really don't see why people would object to this idea. is it because people
are only writing 100 line programs in ruby that might require one abstract
method? i just don't understand how someone could think that diving into a
complex inheritence hierarchy of 30 classes/modules requiring 20 or so abstract
methods would NOT be made easier to grok if one could grep for
/abstract_method/ in their editor to get a jump on things...

my 2cts.

kind regards.

-a

--
share your knowledge. it's a way to achieve immortality.
- h.h. the 14th dali lama

[...]

Duck typing is one thing and abstract method is another.

hi kwatch,
if you want serious discussion then it would be great if you were able to
tell us why you think so.
a statement (alone) is nothing worth if it is not supported by reasonable
arguments.

-- henon

···

On 3/12/06, kwatch <kwa@kuwata-lab.com> wrote:

I think then we have a difference of opinion of the meaning and usage of abstract methods.
I am of the opinion that they exist to enforce stronger typing, especially at compile time. It's a solution to the problem of having a class we're the implementation of a given method is given to a child class but all methods available to a class must be known at compile time. It's especially helpful when the abstract method must return something, and there is no sane default.

class A {
    int someAbstractMethod( ) {
          // If I don't have abstract methods, what should I return?
          // whatever I return, it won't be correct and it won't force child classes
          // to implement me
    }
}

OTOH in ruby since it's dynamically typed, the compiler won't complain about undefined methods until they actually get called (if they are still undefined)

eg
class A {
     someOtherMethod( ) {
            x = someAbstractMethod( ); // forces child classes to implement it simply by calling it
     }
}

Now if you want to have an argument about static vs. dynamic typing that's one thing. You say that it's important that code be descriptive. You also say that documentation is insufficient. I disagree, and say no code is possibly descriptive enough. Whether attempting to call a method
that relies on an "abstract" method raises a NoMethodError or a NotImplementedError the result is the same. The programmer must go read the documentation (or possibly the calling code) to figure out what was expected of him.

Basically, I feel abstract methods are a primarily feature of static typing and help ensure the correctness of your code. But they are a typing feature and the error message you get from them isn't any more descriptive than "function max expected int, got string"

Tangentially related to this, I find your Visitor example a little weak, since your Visitor module provides no methods of its own. Modules are not Java interfaces, and the only reason to write "include Visitor" in your code is to say
# this class implements the methods required to visit other objects
I think you've turned the idea of descriptive code into a degenerate case of ALL this code does is describe. It has no functional purpose. It's like using no-ops to spell out documentation in morse-code :slight_smile: (admittedly, that analogy is a bit ridiculous).

Feel free to ignore the previous paragraph by saying something along the lines of "Well of course a real visitor module would have additional methods, it was just an example." :slight_smile:

···

On Mar 12, 2006, at 2:38 AM, kwatch wrote:

I think it is very important that the code itself is descriptive.
The following code doesn't describe that method each() is abstract.

Logan,

Oh look, this code already raises an exception, a NoMethodError. Now
I know that to include Enumerable I have to have an each method.
That's all there is to it.

I think it is very important that the code itself is descriptive. The
following code doesn't describe that method each() is abstract.

  module Enumerable
    def map
      arr =
      each { |elem| arr << yield(elem) }
      arr
    end
  end

Actually, the method #each is not abstract in Enumerable. It's a
prerequisite. There's a difference. In the C++ and Java world, it's
*necessary* to define a prerequisite method as an abstract method,
because without a method definition, the compiler goes stupid and can't
find anything and won't then compile your code at all. Ruby sees the
call to #each with a block and says "okay, I'll trust that you've
satisified this until I hit it at run time."

The following code itself describes that method each() is abstract.
It's more descriptive.

  module Enumerable
    abstract_method :each
    def map
      arr =
      each { |elem| arr << yield(elem) }
      arr
    end
  end

Please take care of reading code, as well as writing code. You may
insist that documentation is sufficient to describe that, but
documentation is assistant and code should be prime.

What you've got is no better than:

  # Every method provided in Enumerable requires that the including
  # class defines an #each method that iterates one item at a time.
  module Enumerable
    def map
      arr =
      each { |elem| arr << yield(elem) }
      arr
    end
  end

Except that it actually adds a little code for what is essentially a
documentation annotation. As I said, it adds little to no value to Ruby
and IMO doesn't belong in the core.

Oh look, this code already raises an exception, a NoMethodError.

NoMethodError is not proper to abstract method, I think.

I think that it is. It says that there's no method for what's being
looked for. Having "abstract_method :each" doesn't add value to that.

Look at your Visitor example for instance,
in ruby there's not even a need for the Visitor module, all you have
to do is implement visit_foo and visit_bar in MyVisitor. In ruby, no
one cares who your parents were, all they care about is if you know
what you are talking about.

Assume that you need to define many visitor classes.

  module Visitor
    abstract_method :visit_foo, :visit_bar
  end

  class Hoge
    include Visitor
    def visit_foo; ... ; end
    def visit_bar; ... ; end
  end

  class Fuga
    include Visitor
    def visit_foo; ... ; end
    def visit_bar; ... ; end
  end

  class Geji
    include Visitor
    def visit_foo; ... ; end
    def visit_bar; ... ; end
  end

The above code itself describes that "class Hoge, Fuga, and Geji
are visotr classes" very clearly.
'Visitor' module may not be necessary when a number of visitor class
is only 1.
But there should be 'Visitor' module in the case that many visitor
classes are needed. It's more descriptive.

Disagree. In Ruby, inclusion of a Module means something. Specifically,
it means that you're mixing in functionality ... not that you're mixing
in warnings or merely documenting things.

    # Items of class Hoge can be visited by classes that know how to
    # #visit_foo or #visit_bar.
    class Hoge
      def visit_foo; ... ; end
      def visit_bar; ... ; end
    end

    # Items of class Fuga can be visited by classes that know how to
    # #visit_foo or #visit_bar.
    class Fuga
      def visit_foo; ... ; end
      def visit_bar; ... ; end
    end

    # Items of class Geji can only be visited by classes that know how
    # to #visit_foo.
    class Geji
      def visit_foo; ... ; end
    end

Start thinking like a duck man.

You mean 'duck typing'? Duck typing is one thing and abstract method
is another.

Not really. As I noted earlier, abstract methods are necessary in
statically typed and linked languages because they're aggressive in
resolving "facts", whereas Ruby is lazy and doesn't try to resolve
things until they're needed.

Ask yourself what is the purpose of an abstract method.
Then ask yourself if that need is already fufilled in ruby.

Abstract method is useful to realize 'descriptive code'.
It increases code maintanancability.

Disagree. IME, it does the opposite and reduces the descriptiveness and
usefulness of the code. Stop thinking in C++ -- in Ruby, abstract
methods simply aren't necessary.

Trust me. You won't find them in Rails, PDF::Writer, or any of a variety
of other large and popular packages.

-austin

···

On 3/12/06, kwatch <kwa@kuwata-lab.com> wrote:
--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

Hi,

The following code itself describes that method each() is abstract.
It's more descriptive.

module Enumerable
   abstract_method :each
   def map
     arr =
     each { |elem| arr << yield(elem) }
     arr
   end
end

I'm afraid that it doesn't work for some cases, for example:

  class Base
    def each
      ...
    end
  end

  class Derived < Base
    include Enumerable
    ...
  end

The abstract "each" method defined in Enumerable overrides the "each"
in the Base class. I'm not against the idea of abstract (or deferred)
method, but sometimes it's not that simple.

              matz.

···

In message "Re: [RCR] abstract method in Ruby" on Sun, 12 Mar 2006 16:38:44 +0900, "kwatch" <kwa@kuwata-lab.com> writes:

A good portion of the GoF patterns rely on abstract methods (visitor,
abstract factory, template method, etc... ) which IMO only shine in
statically typed languages. It would be a shame to port all patterns
(and their required prerequisite: abstract methods) simply because
they are recognized in the static world and therefore would make a
good fit in Ruby.

The additional code needed for the ruby "abstract method" approach
doesn't make the code any more readable. The extra LoC actually
slows down the maintenance developer as she has to wade through more.

i disagree strongly here. check out these two snippets that i am
working on as we speak:

     def will_process_holding *a, &b
       raise NotImplementedError
     end
     def process_holding *a, &b
       raise NotImplementedError
     end
     def will_process_incoming(*a, &b)
       raise NotImplementedError
     end
     def process_incoming(*a, &b)
       raise NotImplementedError
     end

vs

     abstract_method "will_process_holding"
     abstract_method "process_holding"
     abstract_method "will_process_incoming"
     abstract_method "process_incoming"

Okay, Ara. Why do these methods need to be abstract and specifically
have code around them? IMO, the *same* question should be asked of
tsort.rb. I think that, in both cases, the answer is the same: they
don't.

Adding a definition for an "abstract_method" really doesn't add
*anything* to the readability of the code that a well-written comment
(which *is* read by rdoc without modification) doesn't provide.

[...]

sure - i could just go full on duck type - but good luck reading the
2000 line satellite image processing library that requires these four
methods (and about 20 others) and trying to figure out which ones you
need to implement. i nice compact listing like this surely helps out
with that task doesn't it?

I'm not sure. As I said earlier, PDF::Writer doesn't use them. In
development versions of PDF::Writer, things like that have appeared --
and promptly been removed because they add clutter, not clarity.

[...]

now, all this starts out at about line 230. this is a little deep is
it not? i think it'd be great of rdoc understood and abstract_method
hook just like it now supports the 'attr_XXX' hooks but, even without
that, i'll take any hint i can get in code like this.

ask yourself this: why did the author not just rely on duck-typing?
why put these methods into the code at all?

I ask, instead, how old tsort.rb is -- and that will generally indicate
why it has defined abstract methods.

In both solutions, an exception is thrown at runtime. The abstract
method approach will not allow the developer to catch it any earlier
than the duck typed default.

unless, using one approach it takes an hour to figure out which
methods to impliment and the other way it does not.

Ara, this ignores the examples I gave early on:

  # All clients of this module must implement #each with no parameters.
  module Enum
    def map
      arr =
      each { |elem| arr << yield(elem) }
      arr
    end
  end

I have just made #each an abstract method without adding any code at
all.

The exception proposed although slightly more readable will not in
practice add any value as Ruby developers are used to seeing the
exception and can narrow in on it expediently.

agreed. the point is to not get the exception in the first place
becausee the code was easier to understand how to hook into.

And I disagree that it makes it easier to read, Ara. I don't write
10,000 line programs, but PDF::Writer is 5,000 lines. Certain items,
especially in current development versions, could *easily* have been
declared as "abstract" (and would have to had been in static languages)
but aren't necessary to do such in Ruby.

i really don't see why people would object to this idea. is it
because people are only writing 100 line programs in ruby that might
require one abstract method? i just don't understand how someone
could think that diving into a complex inheritence hierarchy of 30
classes/modules requiring 20 or so abstract methods would NOT be made
easier to grok if one could grep for /abstract_method/ in their editor
to get a jump on things...

I really don't think that there's need for this in the core. I think
that the use of it is a crutch; with the programs you have to write,
Ara, that might be necessary. (I've used similar crutches in some
versions of PDF::Writer experiments.)

-austin

···

On 3/13/06, ara.t.howard@noaa.gov <ara.t.howard@noaa.gov> wrote:

On Tue, 14 Mar 2006, ilan berci wrote:

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca