Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Re: isa() on any scalar

by revdiablo (Prior)
on Jun 10, 2005 at 17:56 UTC ( [id://465619]=note: print w/replies, xml ) Need Help??


in reply to isa() on any scalar

I want my node class to be inheritable, but ultimately, the "parent" of any object should at least be of type Node.

Personally, I don't like using class membership to determine whether an object is "ok" or not. I mean, someone could use Node as a base class, but override all the methods to do anything they want anyway, so what's the point of looking for it? I'd rather go with what is sometimes called duck typing. That is, if it walks like a duck, and talks like a duck, it must be a duck. In Perl terms, this means I like to use can to check if an object has the methods I am going to call on it. For example:

sub use_object_somehow { my ($obj, $foo, $bar) = @_; carp "Object doesn't quack right" and return unless $obj->can("foo") and $obj->can("bar"); $obj->foo($foo); $obj->bar($bar); }

This test is simple enough that I don't think it's too onerous to include in any subroutine that handles an incoming object, but it can be abstracted into a subroutine if so:

sub _check_obj { my ($obj, @methods) = @_; for (@methods) { return unless $obj->can($_); } return 1; }

Then it would be used like:

sub use_object_somehow { my ($obj, $foo, $bar) = @_; carp "Object doesn't quack right" and return unless _check_obj($obj, qw(foo bar)); $obj->foo($foo); $obj->bar($bar); }

Replies are listed 'Best First'.
Re^2: isa() on any scalar
by mrborisguy (Hermit) on Jun 10, 2005 at 18:32 UTC

    Thank you for your post. I'm going to spend some time re-thinking my design, and seeing if this is a better way to do things.

    Bear with me as I reply to your post... I'm certainly not replying because I think your way is inferior to the way I do it, because I'm new to this, so I'm not too set in my ways. But here I go...

    Personally, I don't like using class membership to determine whether an object is "ok" or not. I mean, someone could use Node as a base class, but override all the methods to do anything they want anyway, so what's the point of looking for it?

    Alright, so what's the point of even using my Node as a base class? If it's going to override my methods and do whatever it wants, there's really no point in inheriting from my class. But I know, this was probably just an overstatement to make a point.

    I'd rather go with what is sometimes called duck typing. That is, if it walks like a duck, and talks like a duck, it must be a duck. In Perl terms, this means I like to use can to check if an object has the methods I am going to call on it.

    This duck typing seems to be allowing for objects that aren't inheriting my class to use my classes methods? What's the point of that? If it's going to use them, why not just inherit?

    However, I don't think I LOSE anything by using your duck typing either. This is what I like about your method, I gain the ability to cater to other classes, as long as they impliment a certain interface (basically, the specific interface of can("foo") and can("bar") ), but I don't lose anything. Basically what I'm looking for to see if an object is or at least inherits from my class probably really does mean I just want to be able to use the methods in this theoretical interface. It probably wouldn't be very good to be able to play with the internals of the object anyway (which might be what I was actually subconsciously thinking about doing).

    Wait... just thinking this through quick, you're free to comment on this thinking (well, actually you're free to comment on anything in this post). I have my methods, which are an interface for users and other classes, right? But I can change how things are stored and how things are done within my class, as long as I don't break "the contract" that the methods won't change. So what about playing with the internals of a class that should be the same as mine? What I'm thinking is that, what if later on I decide that I tend to try to find a Node's children more often than it's parent? I've made a promise that I won't change my 'parent' method, but if I just modify some things, I can make 'node' store an array of children, so my $self->parent( $parent ) call might do something like push @$parent->{ children } $self;. Would that be a bad thing? (I'm not sure, I'm actually asking.) [Oh, turns out I WAS subconsciously considering playing with the internals of another object, even if it's supposed to be the same class]. Okay, so if I did end up thinking this was better, how can I modify my classes internally without breaking the contract? Or does this mean I have to do a little bit more planning up front, and that actual "not breaking the contract" deal is more for optimization of functions and storage and the like?

    Once again, I re-iterate that I'm not questioning your judgement, in fact I respect it. I'm just asking questions and probing to see if I can a) think some things through myself, and b) learn from more experienced monks' wisdom.

        -Bryan

      I've made a promise that I won't change my 'parent' method, but if I just modify some things, I can make 'node' store an array of children, so my $self->parent( $parent ) call might do something like push @$parent->{ children } $self;. Would that be a bad thing?

      Yes it would. Better would be to have the parent itself take responsibility for such caching, by supplying a suitable method:

      sub children { my $self = shift; $self->{cached_children} ||= do { # find the children, and end up with (say) an array \@result; }; @{ $self->{cached_children} }; }

      By having the method that knows how to find the children be responsible for caching it, someone using a different implementation gets to override the children method, supply the appropriate logic for locating the children, and make their own judgement on whether to cache or not.

      In an ideal world, then, any instance method would never modify the innards of any object except the instance it was called on - any other objects should be accessed by method calls only.

      Hugo

      Alright, so what's the point of even using my Node as a base class? If it's going to override my methods and do whatever it wants, there's really no point in inheriting from my class.

      That's exactly right. Sometimes there really is no point in inheriting from your class. By checking for isa("Node"), you're requiring the user to do so, even if there is no other reason. The duck typing lets them use inheritance if it makes sense, or a complete reimplementation if that makes more sense. It gives the user more flexibility.

      PS: Sorry I didn't reply to your later thoughts, I haven't had a chance to digest them all the way. I might revisit this post later and see if I have anything more to say.

Re^2: isa() on any scalar
by shemp (Deacon) on Jun 10, 2005 at 18:10 UTC
    One potential caveat is that can() will report false if the method in question is handled by autoload, and is not in the symbol table.

    Of course AUTOLOAD is pure evil and must be stopped. (being facetious). But seriously, this can be an issue

      can() will report false if the method in question is handled by autoload

      Anybody who uses AUTOLOAD in this way should also override can appropriately.

        Thats a very good point, i hadn't really thought about overriding can().

        Then again i have learned to stay away from AUTOLOAD whenever possible.

Re^2: isa() on any scalar
by tlm (Prior) on Jun 10, 2005 at 21:51 UTC

    Personally, I don't like using class membership to determine whether an object is "ok" or not. I mean, someone could use Node as a base class, but override all the methods to do anything they want anyway, so what's the point of looking for it? I'd rather go with what is sometimes called duck typing. That is, if it walks like a duck, and talks like a duck, it must be a duck.

    This is reminiscent of Java interfaces. The techniques to do Java-like interfaces in Perl that I know of rely on creating a base class in which all the mandatory methods die, which forces the children classes to implement them. With this approach, however, the simplest way to test for conformance to the interface would still be an isa-type test. See Java-style interfaces in Perl.

    My main objection to simply testing for the ability to execute certain methods is that, except for the simplest interfaces, it is unlikely that a class will fully implement an interface, including giving the correct names to all the methods, without the author being previously aware of the specs for the interface. If the author is aware of the specs, and these specs are enforced through an interface-like class as described above, then they may as well stick this interface-class in the appropriate @ISAs, and use isa to check for compliance.

    the lowliest monk

      This is reminiscent of Java interfaces.

      That's not what I was getting at. I didn't mean to check for every method that the object "should" have every time you take it in. Notice what I said in my original post:

      I like to use can to check if an object has the methods I am going to call on it

      I have highlighted the point you seem to have missed. I only check for the methods I am actually going to use. This way, someone can implement only the part of the interface that they need to, instead of the whole thing. Like I said in a later reply to the OP, it's about flexibility for the end user.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://465619]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (7)
As of 2024-04-23 17:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found