Abstract method in Ruby

Quoting [email protected]:

sure. this one’s a bit more robust since it doesn’t mask errors:

   def abstract_method m
     define_method(m) do |*a|
       begin; super; rescue NoMethodError; raise

NotImplementedError, m; end
end
end

Hmm, what about cases where the super exists, but calls something
which itself raises a NoMethodError?

-mental

Oops, my apologies, forgot an end in example.
Should read
class B < A
def initialize
@a = “foo”
end
end

Another though on this is that it would allow us to define message
interfaces (aka duck-typing prototypes).

–Bill

Bill B.
i-name: xri://=Bill.Barnhill (For more info:
http://2idi.com/grs/index.php?referral_code=communitivity
)

Hi –

On Tue, 14 Mar 2006, Bill B. wrote:

Oops, my apologies, forgot an end in example.
Should read
class B < A
def initialize
@a = “foo”
end
end

Another though on this is that it would allow us to define message
interfaces (aka duck-typing prototypes).

I fear that “duck-typing prototype” is a contradiction in terms.
With duck typing, you’re dealing with an object: it’s all about the
moment you send the message to the object, and the absence of
concern about where that object came from, what its pedigree is, and
so forth. “Message interfaces” suggests a higher-level clustering of
behaviors.

should_respond_to_behavior  #..options hash describing what to do if
   @a = "foo"

end

B.new
=begin
At this point the class a checks responses:
… it expects a response to messages :foo and :bar with any or no params
… it expects a response to baz with three params
… raises exception if either check fails, or logs, depending on
configuraiton

Having a class police the behavior of its instances after they’ve
already been created strikes me as being at odds with some of the
basic conditions of Ruby runtime – namely, that objects can change
and aren’t constrained by the circumstances of their creation. I
suspect it would also be likely to discourage duck typing.

David


David A. Black ([email protected])
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

“Ruby for Rails” chapters now available
from Manning Early Access Program! Ruby for Rails

Good points both, thanks.

If I have the constraint that objects within a certain group must be
able to
respond to the methods that make up behavior X, then what would be the
best
way to ensure that?

If my group is represented by a collection then I can check for msg
responding on adding to the collection, but if I have a large number of
methods to check, and several different collections, then I’d rather not
have respond to code in each collection’s insertion method.

If I declare a delegate at beginning of class with required methods
defined,
then as I def those methods later in class the delegate methods will get
replaced, right? Is that then the Ruby way to do this?

The behavior I am aiming for is essentially a ‘kind_of?’ for duck
typing,
which as you pointed out is mixing two opposite philosophies. But I can
see
usefulness of something like pattern_of?..ahh, maybe a light bulb is
going
off here. Would this then be the ruby way:
… patterned_obj.pattern_of?(pattern_obj) that does the following
… Gets symbols for public methods on pattern_obj
… Check to see that patterned_obj responds to all these methods

Still doesn’t check for param count though, but it sounds like that may
be a
good thing, as Ruby makes nice use of different param types and
counts…example being Hash.each.

–Bill

Bill B.
i-name: xri://=Bill.Barnhill (For more info:
http://2idi.com/grs/index.php?referral_code=communitivity
)

Hmm, just some thoughts.
I think the abstract method idea nice in concept, but implementation
perhaps
too Java-ish.

If I get a chance later this week I’ll code this, but for now here’s how
I’d
envision something like this within Ruby code:

class A
should_respond_to_behavior #…options hash describing what to do if
msg
not
# responded to properly
goes
here,
# nice to support YAML as
well…
should_respond_to :foo, :bar => {}, :baz => {:param_count => 3}
end

class B < A
def initialize
@a = “foo”
end

B.new
=begin
At this point the class a checks responses:
… it expects a response to messages :foo and :bar with any or no params
… it expects a response to baz with three params
… raises exception if either check fails, or logs, depending on
configuraiton

You could extend the above by having behavior contrained to
normative language…
… should_respond_to just logs warning if msg not responded to properly
… must_respond_to raises exception if msg not responded to properly

Even the following, though HTBD (Here there be dragons…i.e. I’m a
little
quesy about this last one)
… may_respond_to
Same syntax as above, but means that any instances can only respond to
the
defined messages. I can see several uses for this, but also see several
misuses and implementing so you could be reasonably sure about
constraint
would be a pain, and might have to be in Ruby internals. This behavior
also
doesn’t match normative language use, perhaps a
… must_only_respond_to

–Bill

Bill B.
i-name: xri://=Bill.Barnhill (For more info:
http://2idi.com/grs/index.php?referral_code=communitivity )

On 3/14/06, Bill B. [email protected] wrote:

If I have the constraint that objects within a certain group must be able to
respond to the methods that make up behavior X, then what would be the best
way to ensure that?

The point of duck typing is that your code automatically ensures it,
without any extra work on your part, by raising exceptions when the
methods aren’t implemented. The bonus you get for relying on your
code to do just-in-time type verification is that the “prototypes”
never get out of sync with the actual requirements of the code,
because the code IS the prototype.

~Avdi

My apologies for double posting, I must have confused Gmail somehow.

I’ve been following the discussion on abstract method declaration with
some interest. It seems to me that the ideal implementation should not
only throw an exception on unimplemented methods, but also must pass
Matz’s example (implementation provided by base classes) and the
method_missing example.

With that in mind, I started to develope an implementation of
abstract_method in a test first way, where you write a test case and
then only implement enough code to make the test pass. Then you write
another test case and only add enough code to make that test pass.
Repeat as needed.

With that in mind, I came up with 5 test cases:

– BEGIN UNIT TESTS ------------------------------------------------
require ‘test/unit’
require ‘abstract_method’

class TestAbstractMethod < Test::Unit::TestCase

This is the basic use case for abstract methods where the abstract

method is declared in the parent class and redefined in the child

class.

def test_can_override_abstract_method
parent = Class.new {
abstract_method :foo
}
child = Class.new(parent) {
def foo
:foo_result
end
}
assert_equal :foo_result, child.new.foo
end

Here we make sure that not implementing the abstract method in the

child class will cause an exception when the method is invoked on

the child object. We don’t particularly care what exception is

thrown, but the exception message must mention the missing method

by name.

def test_unimplemented_abstract_method_throws_exception
parent = Class.new {
abstract_method :foo
}
child = Class.new(parent) {
}
begin
child.new.foo
fail “Oops”
rescue Exception => ex
assert_match /\bfoo\b/, ex.message
end
end

Now we make sure that our implementation passes Matz’s example

where an abstract method in a mixin is actually implemented in the

base class. We need to make sure that the mixin doesn’t hide the

implemented behavior.

def test_abstract_method_in_mixin_may_be_implemented_in_base_class
abstract_mixin = Module.new {
abstract_method :foo
}
parent = Class.new {
def foo
:foo_result
end
}
child = Class.new(parent) {
include abstract_mixin
}
assert_equal :foo_result, child.new.foo
end

This is a similar scenario to the previous test case where we make

sure the abstract declaration doesn’t interfer with implemented

behavior. This time the implemented behavior is provided by the

method missing technique.

def test_abstract_method_may_be_implemented_by_method_missing
parent = Class.new {
abstract_method :foo
}
child = Class.new(parent) {
def method_missing(sym, *args, &block)
:foo_result
end
}
assert_equal :foo_result, child.new.foo
end

Finally we want to ensure that +abstract_method+ can take multiple

method names, and that the method names may be either strings or

symbols.

def test_abstract_method_may_take_multiple_string_or_symbol_arguments
parent = Class.new {
abstract_method :foo, “bar”, “baz”
}
child = Class.new(parent) {
def foo
:foo_result
end
def bar
:bar_result
end
}
assert_equal :foo_result, child.new.foo
assert_equal :bar_result, child.new.bar
begin
child.new.baz
fail “Oops”
rescue Exception => ex
assert_match /\bbaz\b/, ex.message
end
end
end
– END UNIT TESTS -------------------------------------------------

And here is the implementation that came of that exercise:

– BEGIN ABSTRACT METHOD IMPLEMENTATION ---------------------------

class Module
def abstract_method(*method_names)
end
end

– END ABSTRACT METHOD IMPLEMENTATION -----------------------------


– Jim W.

On Mar 14, 2006, at 9:07 AM, Jim W. wrote:

And here is the implementation that came of that exercise:

– BEGIN ABSTRACT METHOD IMPLEMENTATION ---------------------------

class Module
def abstract_method(*method_names)
end
end

– END ABSTRACT METHOD IMPLEMENTATION -----------------------------

Priceless.

James Edward G. II

Jim W. schrieb:


And here is the implementation that came of that exercise:

class Module
def abstract_method(*method_names)
end
end

LOL!!! Very nice! You should post this to the TDD mailing list.

Regards,
Pit

I’ve been following the discussion on abstract method declaration with
some interest. It seems to me that the ideal implementation should not
only throw an exception on unimplemented methods, but also must pass
Matz’s example (implementation provided by base classes) and the
method_missing example.

:slight_smile: I like your approach, and I think it’s extremely close. I’d change
one of your tests a little though:

Here we make sure that not implementing the abstract method in the

child class will cause an exception when the method is invoked on

the child object. We don’t particularly care what exception is

thrown, but the exception message must mention the missing method

by name.

def test_unimplemented_abstract_method_throws_exception

It would be preferable I think, if a more helpful error message were
given, instead of simply the method being missing.

And while I suspect you’re being a little flipant, I like your
implementation - it does do nearly everything that’s desired.

Ok, I think I see what you mean. I didn’t have the right understanding
of
duck-typing and your explanation of how it works make a lot of sense,
though
coming from Java the idea makes my stomach a bit queasy as I relied on
Interfaces a lot.

Thanks for help,
–Bill

Hi –

On Wed, 15 Mar 2006, [email protected] wrote:

child class will cause an exception when the method is invoked on

implementation - it does do nearly everything that’s desired.
I don’t think Jim is being flippant at all. He’s provided a fantastic
illustration of something that’s true generally about Ruby, namely
that it is incredibly expressive. Many of the twists and turns one
takes in trying to get Ruby to do, or be, this or that end up adding
up to a slip-knot; and what you really have to do is something that
looks negligible.

I’m happy without abstract methods in Ruby but it’s worth the lengthy
discussion to get to Jim’s post :slight_smile:

David


David A. Black ([email protected])
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

“Ruby for Rails” chapters now available
from Manning Early Access Program! Ruby for Rails

unknown wrote:

It would be preferable I think, if a more helpful error message were
given, instead of simply the method being missing.

Agreed. The unit test only specifies that the method name should be
mentioned. I’m not sure how to test for a “helpful” error message
without specifying the exact text to be shown … and I didn’t want to
overconstrain the solution.

Perhaps Nathaniel will include an assert_helpful_error_message method in
the next release of Test::Unit.

And while I suspect you’re being a little flipant, I like your
implementation - it does do nearly everything that’s desired.

What? Me flippant? Never!

:wink:


– Jim W.

Jim W. wrote:

– BEGIN ABSTRACT METHOD IMPLEMENTATION ---------------------------

class Module
def abstract_method(*method_names)
end
end

– END ABSTRACT METHOD IMPLEMENTATION -----------------------------

It would be nice if it made the following fail somehow:

class A
abstract_method :foo
def foo; end
end

And also in this case:

class A
def foo; end
abstract_method :foo
end