[RCR] abstract method in Ruby

B

Bill Barnhill

------=_Part_3880_1031733.1142348292366
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

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

------=_Part_3880_1031733.1142348292366--
 
A

Avdi Grimm

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 be= st
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
 
J

Jim Weirich

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 -----------------------------
 
B

Bill Barnhill

------=_Part_4016_21132757.1142349256270
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

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, thoug=
h
coming from Java the idea makes my stomach a bit queasy as I relied on
Interfaces a lot.

Thanks for help,
--Bill

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

------=_Part_4016_21132757.1142349256270--
 
J

James Edward Gray II

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 Gray II
 
P

Pit Capitain

Jim said:
...
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
 
B

benjohn

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.

:) 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.
 
D

dblack

Hi --

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

...

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.

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 :)


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! http://www.manning.com/books/black
 
J

Jim Weirich

unknown said:
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!

;)
 
J

Joel VanderWerf

Jim Weirich 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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,570
Members
45,045
Latest member
DRCM

Latest Threads

Top