http://www.perlmonks.org?node_id=229704


in reply to OO-style modifiers for 'sub' ?

As other people have pointed out, checking that you are a specific type is just not-Perl. In fact, its not OO either, because it disables inheritance.

C++/Java approach would be to define base classes (as interfaces) and inherit from them. The you can say

sub foo { my ($self) = @_; croak "..." unless ref($self) && $self->isa("AnInterface") }
But this would have problems: First, you may need to check multiple interfaces; second you have to include them in your ISA list; third, its all getting very verbose, and not-Perl.

So lets turn it around a bit:

sub foo # requires interfaces InterfaceA, InterfaceB { my ($self) = @_; InterfaceA::validate($self); InterfaceB::validate($self); }
This client code is much simpler: you are passing the implementation of the check down to a module that knows what it means to be that module. An you no longer require explicit inheritance:
sub InterfaceA::validate { my ($candidate) = @_; error unless ref($candidate); error unless $candidate->isa("InterfaceA"); # explicit ISA } sub InterfaceB::validate { my ($candidate) = @_; error unless ref($candidate); error unless $candidate->can("fn1"); # implicit ISA error unless $candidate->can("fn2"); }
Ignoring the issue of naming, it is clear that having an interface know what methods are needed is more powerful than requiring explicit inheritance. If an object supports all the methods in an interface, then it can be said to implement that interface.

One last thing: the names I've chosen are non-optimal, but if validation is common then you'd think that you want concise names. But you can work around this. We might name our validation methods "is_implemnted_by", and create a wrapper function (in a shared utilities module):

sub validate_object { my ($self, @interfaces) = @_; error unless ref($self); foreach my $interface (@interfaces) { my $fn = $interface . "::is_implemnted_by"; error unless $fn->($self); } } #... sub foo { my ($self) = @_; validate_object($self, qw/InterfaceA InterfaceB InterfaceC/); } sub bar { my ($self) = @_; validate_object($self, map {"Interface$_"} qw/A B D/); }
--Dave

Replies are listed 'Best First'.
Re^2: OO-style modifiers for 'sub' ?
by Aristotle (Chancellor) on Jan 24, 2003 at 23:59 UTC
    sub foo # requires interfaces IntA, IntB { my ($self) = @_; IntA::validate($self); IntB::validate($self); }
    Excellent! I am impressed - a very simple, concise and - as far as I can tell - robustly versatile approach. Seems to be the perfect distribution of responsibilities. It certainly deserves attention - if it doesn't get much here, please repost it as a root node. I'm very curious to hear the gurus with theoretical background poke at this and comment on it.

    Makeshifts last the longest.

Re^2: OO-style modifiers for 'sub' ?
by adrianh (Chancellor) on Jan 26, 2003 at 23:53 UTC
    As other people have pointed out, checking that you are a specific type is just not-Perl. In fact, its not OO either, because it disables inheritance.

    All the tests that have been proposed so far work with inheritance. The inheritance problem is when the subroutine isn't called as a method.

    C++/Java approach would be to define base classes (as interfaces) and inherit from them. The you can say

    sub foo { my ($self) = @_; croak "..." unless ref($self) && $self->isa("AnInterface") }

    But this would have problems: First, you may need to check multiple interfaces; second you have to include them in your ISA list; third, its all getting very verbose, and not-Perl.

    If you had a base class (or interface) it would be part of the class hierarchy. So you would have:

    use base qw(AnInterface AnotherInterface YetAnotherInterface); sub foo { my $self = shift; ... code ... };

    Which will be enough (as long as you call foo as a method) to ensure $self is of the appropriate class. Not verbose at all!

    I've argued elsewhere that if you don't call foo as a method you deserve all you get :-)

    So lets turn it around a bit:

    sub foo # requires interfaces InterfaceA, InterfaceB { my ($self) = @_; InterfaceA::validate($self); InterfaceB::validate($self); }

    This client code is much simpler: you are passing the implementation of the check down to a module that knows what it means to be that module. An you no longer require explicit inheritance:

    I'm covering the same ground as in Class::Interface -- isa() Considered Harmful - but surely these sort of relationships are exactly what ISA is all about?

    I don't understand what the advantage of avoiding explicit inheritance is? I've yet to see an example that wouldn't (in my opinion) be better handled by ISA relationships or delegation.

    Can somebody show me what I'm missing? :-)

      Can somebody show me what I'm missing? :-)

      Perhaps a good starting point would be to google C++ metatemplate programming. In fact, just consider any simple C++ template function, e.g.

      template<class T1, class T2> bool max(T1 a, T2 b) { return a < b ? b +: a; }
      Any object-pair that supports the less-than operator can be used with this function. There is no explicit C<Comparable> interface -- and there's no loss of static type safety. You ask: "I don't understand what the advantage of avoiding explicit inheritance is?": I'd turn that round: what advantage would be gained by requiring an explicit interface? Once you've convinced yourself that, in the context of C++, the answer is "none": then we can consider the context being Perl. --Dave

        I've coded C++ and grok templates. They solve some problems quite neatly, bearing in mind C++'s design focus on efficiency.

        The thing you lose by having templates like the above is an explicit representation of the classes contract. You have the developer saying "<" has the same semantics in all of the classes involved - rather than it being enforced by the type system. Yes, there is no comparable interface - and that's a mistake.

        So, to answer you question:

        What advantage would be gained by requiring an explicit interface?

        The advantage of having an explicit interface is - that you have an explicit interface :-) By saying something belongs to a particular class you are saying that it functions in a certain way, that it forfills a given contract.

        I might have a class with a method set_pitch(N). If its a subclass of MusicalInstrument I know N is Hertz. If its a subclass of aircraft I know N is degrees. Statements about an objects class stop me doing the wrong thing to the wrong object.

        Templates are handy in C++ but they're not really relevent to perl since it's not statically typed.

        They're also not the only solution to genericity with static typing. For example, Eiffel's generic classes allow more sophisticated statements about inheritance to be made so you can say things like:

        class SORTED_LIST [G -> COMPARABLE]

        (you can only make SORTED_LISTs out of things that are COMPARABLE).

Re: Re: OO-style modifiers for 'sub' ?
by l2kashe (Deacon) on Jan 27, 2003 at 06:32 UTC
    I was thinking something along these lines while in the shower. (Best place to code) :P..

    Anywho... Thinking about the problem space, and what is needed, it just felt wholly unPerish to write the same or very similar code time and again.

    My actual thought was implementing a group of methods, possibly a seperate Class, which did validation. Then providing it a list of method names which needed to be within the inheritance tree. Something along the lines of maybe
    use Validate; ($ok, $err) = $obj->validate( isa => [this, that, theother], can_do => [meth1, meth2, meth3], ); croak $err if (!$ok);
    If they existed, then who cares who's calling. Then I thought "What if they override a prerequisite method within their own space"?.. Well if they do, then A) They know what they are doing, hence can use our code or B) They have no clue what they are doing, can't quite figure something out, or are trying to shutgun something together and deserve what ever quirks pop up.

    So I haven't really added anything to this thread, so I'm sorry. But I like the post Im replying to and its simplicity, and just wanted to pipe up that its how I was thinking of doing it as well, so an additional ++ to you cause I only have 1 vote for ya.


    /* And the Creator, against his better judgement, wrote man.c */