Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

Why breaking can() is acceptable

by tilly (Archbishop)
on Apr 06, 2004 at 01:55 UTC ( [id://342804]=perlmeditation: print w/replies, xml ) Need Help??

This is a personal opinion that chromatic and I have a long-standing disagreement about. I keep on not promoting it to a root node because I don't think that I have time to discuss it right, but since he keeps tweaking, I'll give in.

The original discussion started at Re: Re: Re: Symbol table globbing from object refs. It's been referred to a number of times, most recently at Re: Re: Re: Re: Testaholics Anonymous (or how I learned to stop worrying and love Test::More).

The problem is that when you implement methods with AUTOLOAD, then the default implementation in UNIVERSAL::can can't tell whether or not you have implemented that method. The issue is what you should do about it. chromatic's opinion seems to be that if you implement methods with AUTOLOAD, you should also leave function stubs so that UNIVERSAL::can can tell that those functions really are implemented. My opinion is that mixing AUTOLOAD and UNIVERSAL::can is fundamentally broken, should be treated as fundamentally broken, and we should just accept that as an inevitable fact of life. Only one person gets to be clever at a time, and the guy who wants to AUTOLOAD, wins.

I'll try to explain my views, and I assume that chromatic will be along in a bit to explain his.

My fundamental issue is that that which is not automated, should not be trusted to be reliable. Particularly if the not automated part has any complex or subtle issue. In other words, the fact that we have to ask everyone to be vigilant, means that people will mess up. How do we then handle those failures?

One approach is to complain. Tell people, You messed up! You didn't take care of this issue! Now nagging never seemed very fun to me, nor does it seem very effective. My personal belief is that either you can convince people to align their goals to yours, or else you have to accept that they aren't likely to do what you want. If you try to push them to do what they have no real reason to want, then expect pushback. (No matter how laudable your goals are, or how much people agree with them in theory.)

So what is the issue? It is that if you implement stuff dynamically (and naively) with AUTOLOAD, and UNIVERSAL::can breaks. Do most people care about UNIVERSAL::can breaking? Not really. In fact most people don't use it. So we are asking people to do extra work for something that they likely don't care about. Which never bodes well for success.

Now let's investigate the nature of that extra work. At the very least we are asking for duplication of information. (The same information to be found in AUTOLOAD is done again in declarations for UNIVERSAL::can.) This kind of duplication is exactly the same as duplications between comments and code. Any experienced, competent programmer should immediately distrust that kind of duplication as being inherently unreliable.

But it gets better. If I've gone to the extent of doing an AUTOLOAD, then I either am (or think that I am) doing something fairly dynamic. Such as keeping code in a database (something that I advise against for different reasons), in random plugins, or even having behaviour that is customized per object. (Yes, I've seen AUTOLOAD used for each of these purposes.) In all of these cases, the code with the AUTOLOAD both does not, and should not know what methods the AUTOLOAD will or will not succeed in implementing. How, then, are we to satisfy the UNIVERSAL::can Nazis and declare the right set of methods? (Particularly when that set may differ depending what object tripped the missing method.) The only approach that I see is to override UNIVERSAL::can, which course opens up its own can of worms.

But wait! I'm not done! The basic nature of AUTOLOAD is that the first AUTOLOAD discovered after the lookup failed gets to handle it. So suppose that we have classes A, B, and C, each of which inherits from the previous one. Class A implements an AUTOLOAD, which predeclares as chromatic wants. Class B does the same. Now what happens if someone calls can on something in class C that is implemented in A but not in B? Well the can will say that it is implemented (it finds the stub in A), but when B::AUTOLOAD is called, it has no straightforward way to decide to politely decline the call and let Perl continue searching for another AUTOLOAD. But, you say, we can just cleverly call SUPER::$method and THAT will find the right AUTOLOAD! Except that we are just moving the breakage, SUPER:: does not play well with multiple inheritance. Instead you need another dependency, NEXT.

So we are asking the person who implements AUTOLOAD to duplicate information (which they may not actually have), and then modify AUTOLOAD itself to follow best practices which not only are not widely documented, but which most developers cannot be expected to figure out on their own.

Now is it hopeless? Is there any way to have the goodness of something like AUTOLOAD and something like UNIVERSAL::can at the same time? I don't accept that, and I'm about to outline a design which makes the two play together.

Create a UNIVERSAL::AUTOLOAD that goes through the inheritance hierarchy looking for a CAN method. If it finds it, it calls that method expecting back a subroutine reference or a false value. If it gets a subroutine reference, it calls it. If it gets a false value, it continues looking. At the same time override UNIVERSAL::can to first try a regular UNIVERSAL::can (you can hold a reference to the original one in a closure), and otherwise do the same search through the inheritance tree for CAN methods that AUTOLOAD does. Convince everyone who wants to use AUTOLOAD that they should use your custom version and write CAN methods instead. And then it all works. You can accomplish anything that AUTOLOAD could. UNIVERSAL::can works. There is no duplication of logic anywhere. (If this explanation confuses and people really want, I could easily write this modelcode. I just don't want to do that in addition to writing this post at the moment.)

Of course when it comes time to actually convince people to switch, you still have to get over their likely indifference to whether UNIVERSAL::can works. Particularly when it means rewriting already working code.

Perhaps in Perl 6 we can encourage people to follow this strategy. But in Perl 5 it is a lost cause, and it strikes me as fairly silly to lecture people on why they should do extra work to reduce breakage in something that they are highly unlikely to get right, and mostly didn't really care about anyways.

Update: I used the wrong word. Fixed.

Update 2: jweed pointed out that both links to past discussion were the same. Oops. Fixed.

Replies are listed 'Best First'.
Re: Why breaking can() is acceptable
by BrowserUk (Patriarch) on Apr 06, 2004 at 02:25 UTC

    This sounds a little like using turn signals (indicators) on a car. The law might mandate it, and it's certainly not a bad thing to do, but driving such that you are commited to relying upon others doing so correctly is fraught with danger.

    The problem with this type of 'rule', is it requires that everyone voluntarily comply with it, before it will ever be safe to benefit from it.

    To extend the analogy a little further, it also suffers in the same way as turn signals in that it is nearly impossible to automate, because it requires you know where you are going before you get to the decision point; but we all have to visit strange cities, and we are all subject to late breaking clues and changes of direction.

    The problem with turn signals, is that just as soon as you get comfortable with relying upon them, you encounter the driver that sets his signal left and then turns right.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail

      I think this is more like hiring a number of people and assigning each one a task, and then following each one around to make sure they do it right. That's right: micromanagement.

      For better or worse, i am with tilly on this one. If i need that kind of Bondage and Discipline, i will code in Java and not Perl. (And i don't code Java ;))

      jeffa

      L-LL-L--L-LL-L--L-LL-L--
      -R--R-RR-R--R-RR-R--R-RR
      B--B--B--B--B--B--B--B--
      H---H---H---H---H---H---
      (the triplet paradiddle with high-hat)
      
Re: Why breaking can() is acceptable
by dragonchild (Archbishop) on Apr 06, 2004 at 02:52 UTC
    or even having behaviour that is customized per object.

    I've got this in one of my production distributions. What did I do? I provided a can() method in that class that uses the same information that AUTOLOAD does. No duplication of information. It's all determined at runtime, anyways. AUTOLOAD needs to figure it out somehow based on the object, so can() should as well.

    Personally, it's the module author's responsability to provide and support the implied interface that we are all accustomed to. If you do funky stuff with AUTOLOAD, then you are obligated to do the exact same funky stuff with can(). It's that simple.

    I'm going to also stop in its tracks the counter-argument that UNIVERSAL::can() will be broken. Well, you know what? The only reason I can think of to call the UNIVERSAL:: version is for isa(), as a really cool version of ref(). If you're not sure if something is even an object, then why the heck are you asking if it provides a given method?!? I'm not sure I would be able to follow such obfuscation. It sure as hell better not be in my codebase cause it ain't gonna pass my code review.

    (Personally, I don't like it when people call UNIVERSAL::isa(), either, cause it prevents me from doing cool things, like pretending I'm not really implemented as an ARRAY in order to fool HTML::Template into treating me like a SCALAR and stringifying me. Scalar::Utils::blessed() is a much better option, but blessed() didn't come into the core till 5.8.x. *shrugs* *makes a note to use blessed() more, now that he ranted about it*)

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      Personally, it's the module author's responsability to provide and support the implied interface that we are all accustomed to. If you do funky stuff with AUTOLOAD, then you are obligated to do the exact same funky stuff with can(). It's that simple.

      Is it also the module author's responsibility to not break multiple inheritance?

      The way that you described doing things underscores my point. If your funky class is in a later chain of inheritance with multiple inheritance, then you just broke can! Exactly what you said that it was your responsibility not to do!

      Depending on how yhou implemented it, you may have broken can with single inheritance as well. (Particularly if the subclass tries to use AUTOLOAD. But one could argue that it is that subclass' responsibility to understand the class that it is overriding.)

      Which is one of the reasons why I kept on saying UNIVERSAL::can. Overriding can in any other location leads to potential breakage. Personally I'm OK with that as long as you're clear on what breaks, and why. (I'm not a big fan of multiple inheritance, so I don't grieve when it dies. Others might.)

      The dominant reason why I said it that way was to keep people from having to figure out whether I was talking about can as a noun or a verb in the same sentences. However the other issue was in the can of worms that I referred to if you try to override can locally. (Hrm, make that a verb or which noun? :-)

        tilly

        First, I will say that if you are using multiple-inheritance in the presence of AUTOLOAD then you are already in trouble. Sure, you should be able to do it, but IMO thats being idealistic. Part of the difficulty of multiple inheritance is managing the dispatching of methods and name clashes, if your methods aren't even implemented (and handled with AUTOLOAD) your just asking for it.

        As for how to implement local can without breaking it elsewhere, I don't see what the problem is. This (untested) code below (written waaaay past my bedtime) should serve as a stating point:

        sub can { # ------------------------------------ # this is the code tilly is talking about below # sorry , it was late # ------------------------------------ # my ($calling_package) = caller(); # if ($calling_package eq __PACKAGE__) # ------------------------------------ # however, this is what I meant ... my ($self, $method_name) = @_; # if this is called with by an object specifically # blessed into this __PACKAGE__, then we # will handle this because of AUTOLOAD if (ref($self) eq __PACKAGE__) { return my_special_can_that_plays_nice_with_AUTOLOAD(@_); } else { return $self->SUPER::can($method_name); } }
        Now I am making the assumption that SUPER::can will work, but its alot easier to control where you inherit from then it is who inherits from you. If SUPER::can doesn't work, then do something that does work.

        I will say again, can should work, period, end of story. If it doesn't work (because you used AUTOLOAD or even some symbol table madness) then you need to re-think your design.

        -stvn
        Is it also the module author's responsibility to not break multiple inheritance?
        Is there any module on CPAN that doesn't potentially break with multiple inheritance? Any object that uses a reference to a hash, and stores its attributes in that hash potentially breaks multiple inheritance. In more than one way.

        Now, if everyone just used inside-out objects.... (but they don't magically solve the AUTOLOAD/can mess).

        Abigail

Re: Why breaking can() is acceptable
by TimToady (Parson) on Apr 06, 2004 at 22:22 UTC
    Interesting discussion. Perl 6 will not need to encourage people to define their own .can. Here's a paragraph from A12:
    By the way, unlike in Perl 5 where .can returns a single routine reference, Perl 6's version of .meta.can returns a "WALK" iterator for a set of routines that match the name. When dereferenced, the iterator gets fed to a dispatcher as if the method had been called in the first place. Note that any wildcard methods (via delegation or AUTOLOAD) are included in this list of potential handlers, so there is little reason for subclasses to have to redefine .can to reflect the new names. It does weaken the meaning of .can from "definitely has a method of this name" to "definitely has one or more methods in one or more classes that will try to handle this." But that's probably closer to what you want, and the best we can do when people start fooling around with wildcard methods under MI.
    Also, we're making a clean separation between AUTOLOAD, which does "wildcard" methods, and AUTODEF, which may only supply a definition for predeclared (stubbed) methods or subroutines. (The delegation syntax also makes a clear distinction between those methods we know the name of in advance and those we don't.)
      I'm sure that I don't really understand the way that you described that, likely because I have not read the rest of A12 yet.

      However if you haven't nailed that part of the specification down, this discussion has convinced me that it would be a definite improvement to have a wildcard decision on whether to handle a method. That is, instead of writing something like an AUTOLOAD, you would write a method named something like CAN, which would dynamically decide whether or not to accept this method, and if it does, returns the subroutine that does it.

      In thinking about it, I've had trouble coming up with anything that you'd want to do with a reasonable AUTOLOAD which cannot readily be duplicated by a CAN that works like this. Furthermore while it is very hard to make multiple inheritance and AUTOLOAD cooperate, this CAN strategy cooperates with multiple inheritance rather smoothly. In fact it returns the best possible for can() to "definitely has a method of this name", even in the face of multiple inheritance and wildcard methods.

      Unfortunately if a user wanted to implement this in Perl 6, as I understand your paragraph they would not be able to pass the extra information to can() that, "I can really tell you whether I'll do that". Which prevents can() from giving the answer that we probably really wanted it to give.

      In fact I'm seriously thinking of writing a module named something like UNIVERSAL::AUTOLOAD_CAN which implements a UNIVERSAL::AUTOLOAD and UNIVERSAL::can that implements this solution in Perl 5. This would make it possible to get can(), AUTOLOAD and multiple inheritance to play together. (But only if you write no other AUTOLOADs!)

        I think this will work out ok if .meta.can, when it's looking for wildcarding classes, distinguishes classes that define their own .can from those that don't, and only adds autoloaders to the end of the list that aren't shielded by a corresponding .can method. (The problems Perl 5 has with defining a .can method in a class shouldn't arise in Perl 6, which tries to prefer "next" semantics to "super" semantics. At worst we'll require people to say "next METHOD" at the end of each such .can method definition.)
      If a package/class has an AUTOLOAD sub, will you be able to decline to handle a method, but say "I don't want to handle this, but keep looking in the inheritance tree"? Say for some reason, you want your class to handle get_* methods, and the next class in ISA (or whatever the Perl 6 equivalent is), to handle set_* methods; nevermind how good/bad of a design this is :)
        Yes, you can always call "next METHOD" to pass the buck to the next method in the list of candidates. Though an AUTOLOAD is going to be pretty far down the pecking order, because Perl will prefer all declared methods over all wildcard methods, and autoloading is one form of wildcarding. So only an AUTOLOAD in a parent class (or a sibling class, since these are "next" semantics) is possible in the remaining list of candidates.

        Though as for get_* and set_* methods, those would be non-standard in Perl 6. Perl's accessor methods will not be split into get and set methods. Rather, they'll be single methods that are lvaluable if you want to allow setting.

      Here's a paragraph from A12:

      And when will be seeing the rest of A12? ;-)

Re: Why breaking can() is acceptable
by stvn (Monsignor) on Apr 06, 2004 at 04:47 UTC

    I'm going to agree with dragonchild, if you are going to do AUTOLOAD stuff then you should implement a version of can for your class. This is basic interface polymorphism (another of chromatic's pet peeves), you should be able to expect that inherited methods called on your object behave in a resonably obvious/intuative way or have a very good reason not too. Sometimes, this means not relying on the default implementation (UNIVERSAL::can) and instead write a custom implementation.

    Part of the idea of using AUTOLOAD is to hide the implemenation (methods not being implemented) from the user of your class. If in doing so you break can (an expected inherited method of the UNIVERSAL base class) then you are breaking the object, and your code is incomplete.

    I would even go so far to argue that if you cannot write a version of can that functions as a transparent replacement, you should re-think your use of AUTOLOAD in the first place.

    I see no reason why it should be left broken or that you would need to make method stubs. It should work as expected, nothing more, nothing less.

    -stvn
      I believe that getting a replacement can method to work absolutely correctly in all situations is far harder than it looks. I tried to explain some (not all!) of the issues in my root node. If you think that it is easier than I do, then post an interesting example of an AUTOLOAD and how you'd override can, and I'll try to demonstrate how - by your standards - your code is incomplete.

      If the task proves difficult for you, right after you just read a description of some of the things that can go wrong, can we both agree that expecting people to consistently get it right is unrealistic?

        I never said it would be easy, I just said that you shouldn't break can for the sake of AUTOLOAD. But I am not a big fan of AUTOLOAD, while I do make (occasional) use of can so I would sooner throw away AUTOLOAD before I gave up can.

        I will take you up on your challange, but with one change. I ask that you post and interesting example of AUTOLOAD, for which I will write a version of can. Partially because I don't like or use AUTOLOAD very much and to be honest would have a hard time coming up with an interesting example, and partially because I am not a fan of writing vaguely specified code for others to shoot holes into. Something like this is clearly complex, I do not deny that, but if you have a solid and well thought out implementation of AUTOLOAD I expect it would be possible to write a version of can to go with it.

        -stvn
        The following snippet is taken from that example I alluded to above.
        # This exists to provide object-specific can() functionality. # 1) If this is an object, check to see if the method is an element na +me. # 2) If either is not true, redispatch to the parent's can() method. + sub can { ( ref($_[0]) && UNIVERSAL::isa( $_[0]->elements, 'HASH' ) && exists $_[0]->elements->{$_[1]} ) || ( $_[0]->SUPER::can($_[1]) ); } sub AUTOLOAD { (my $name = our $AUTOLOAD) =~ s/.*::([^:]+)$/$1/; my $self = shift; unless ($name eq lc $name || exists $self->element_classes->{lc $n +ame}) { return eval "$self->SUPER::$meth(@_);"; } $self->elements->{$name} = shift if @_; $self->elements->{$name}; }

        A few notes:

        1. This is a container object, providing a unified interface to parsing stuff. It acts as a record and the elements are the various columns. Each record may have different columns and the goal was to provide a way of allowing the user to call the column name as a method and get the column object.
        2. element_classes is a method which returns a hashref of name-class pairs. The name is the name of the column as provided to this object in the constructor. The class is the class of the column.
        3. elements is a hashref which actually contains the column objects in name-object pairs. It is guaranteed that the names from element_classes() will be identical to the names from elements().

        This implementation has the added benefit of the fact that it's currently working in production. Now, nothing inherits from it (yet) and I do not use MI in production code, for the reasons well-cited elsewhere.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

Re: Why breaking can() is acceptable
by hardburn (Abbot) on Apr 06, 2004 at 13:57 UTC

    I'd prefer that people stop using AUTOLOAD when they don't need to. Specifically:

    sub AUTOLOAD { my $self = shift; $self->{$AUTOLOAD} = shift if @_; $self->{$AUTOLOAD}; }

    The actual code could be more complex, such as checking $AUTOLOAD against a list of allowed attributes, but the idea of runtime handling of accessors/mutators is the same. If you must have autogenerated accessors/mutators, please use symbol table manipulation instead if at all possible:

    # Could be wrapped in a BEGIN block if you wanted to foreach my $field (qw/ foo bar baz /) { *$field = sub { my $self = shift; $self->{$field} = shift if @_; $self->{$field}; }; }

    This is faster, is as memory-efficient as things get around Perl, and (IIRC) doesn't break can. Overall, it's even better to avoid spreading accessors/mutators all over your class design in the first place, but that's a seperate issue.

    I understand the above isn't the only use of AUTOLOAD, but it's by far the most common. So if you're doing that, please stop and look for alternatives. Or better, fix your class design.

    ----
    : () { :|:& };:

    Note: All code is untested, unless otherwise stated

      And that is exactly what I was referring to with my comments on "typeglobbing closures instead of AUTOLOAD".

      I also usually don't wrap it in a BEGIN block. In the context of a module, there is no real reason to wrap it in a BEGIN block, because the module itself is wrapped in an implicit BEGIN block. Otherwise I prefer to not try to specify BEGIN ordering unless absolutely needed, instead specify ordering of events as simple as I can arrange. (But that was an old discussion with tye.)

      Ok. I agree with you, for that instance. However, how do you handle syntactic-sugar methods that are instance-specific?

      ------
      We are the carpenters and bricklayers of the Information Age.

      Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        how do you handle syntactic-sugar methods that are instance-specific?

        That is something that probably comes down to a case-by-case basis, so I don't want to give any hard rules on it. What I do want is to stop people from using AUTOLOAD to handle accessors/mutators when other solutions are available. This seems to be the most common use of AUTOLOAD, and it needs to stop. (Please direct flames twards tchrist :)

        ----
        : () { :|:& };:

        Note: All code is untested, unless otherwise stated

Re: Why breaking can() is acceptable
by Ovid (Cardinal) on Apr 06, 2004 at 22:37 UTC

    For the most part, I agree with your assessment of the problem. However, I don't know that I agree with your solution because your solution attempts to deal with fundamental problems in Perl's OO model. Here are my thoughts.

    First, programmers tend not to worry about these issues (and are less likely to encounter them) if they're building small systems. If they're building large systems, those programmers had durn well better know about those issues.

    Second, I don't believe it is appropriate for programmers to attempt building large systems without a test suite. A good test suite will nail problems like this. If you find a bug, you add a test. This might seem to be irrelevant to your concerns, but I've found that with tests, while I write better code, I also worry less about constructs that I previously would have avoided due to apparent fragility.

    Third, these issues are, of course, exacerbated when multiple inheritance is used, but programmers should usually be be delegating instead of inheriting. If UNIVERSAL::AUTOLOAD is searching a huge inheritance heirarchy, I submit that the system probably has some design issues. Scalability in Perl should usually not be achieved via complicated inheritance heirarchies. That's begging for trouble. Abigail's inside out objects can help, as can lexically scoped subs for private methods (my $sub = sub foo{};) and traits, but the reality is, Perl's OO has some quirks that people should be paying attention to. can seems to be pretty far down the list.

    Cheers,
    Ovid

    New address of my CGI Course.

Re: Why breaking can() is acceptable
by Beechbone (Friar) on Apr 06, 2004 at 08:21 UTC
    I think this discussion is pointless. You either provide an interface to your module---then you decide if it supports can()---or you implement an interface another module expects---then the author of that module decided if can() is needed. That's it.

    Implementing a working can() isn't that hard. I'd say this should work (ATTN: untested, WILL contain syntax errors):

    sub can { my ($self, $what, $code) = (@_[0,1], undef); return $code if $code = $self->UNIVERSAL::can($what); return $code if $code = $self->_can($what); foreach my $superclass (@ISA) { return $code if $code = eval "\$self->${superclass}::can(\$wha +t)"; } return undef; } sub _can { # aka _load() my ($self, $what) = @_; if ($what eq 'foo') { return sub {'foo'}; } else { return undef; } } sub AUTOLOAD { my ($self, $code) = ($_[0], undef); goto &$code if $code = $self->can($AUTOLOAD); die "oops"; }
    PS: This even provides a working way for using AUTOLOAD with multi-inheritance, I'd say.

    Search, Ask, Know
      If everyone had your attitude, then I wouldn't have seen a point in having the discussion either. The issue is that there are people who think that any module which decides that can() shall not be supported is broken. Since you apparently agree with me that breaking can() is OK, we would have had little to discuss.

      As for the solution that you provided, it tries to do a good job, but still has several subtle issues with it. First of all, and glaringly, your $AUTOLOAD always includes the package name and so will fail to do any kind of inheritance. That can (and should) be readily fixed. Secondly your AUTOLOAD fails (uninformatively) if you accidentally call an unimplemented function procedurally in any class that inherits from it. Thirdly if you appear partway up the class hierarchy in a multiple inheritance scheme, you will implicitly prune the class hierarchy for AUTOLOADed stuff to that hierarchy, contrary to what you claim about it being a working way for using AUTOLOAD with multiple inheritance. That is a common AUTOLOAD problem, your can matches what your AUTOLOAD does. (And it is a common multiple inheritance problem, people tend to think about the cases where they are at the bottom or top of the hierarchy, and don't think through being in the middle of the inheritance tree.) And finally it is worth noting that anyone who tries to do what I suggested with a UNIVERSAL::AUTOLOAD to allow can() and AUTOLOAD to play together will find that things implemented with their AUTOLOAD override yours. (Ironically this is probably a good thing.)

What is this can() and why is breaking it (un)acceptable?
by jonadab (Parson) on Apr 06, 2004 at 14:29 UTC

    Tilly, your arguments make sense to me, but I'm previously unfamiliar with this UNIVERSAL::can; I've never _heard_ of it before, much less used it. I've certainly heard plenty about AUTOLOAD and understand some of the things it would be useful for, though I've never used AUTOLOAD either. But if I ever needed something AUTOLOAD could provide, I might use AUTOLOAD, and up to this point I would not have even thought about this can thing.

    So I guess what I want to hear from the people who are saying that nobody should ever break can is, why is it important for every module to work with can? For example, I have a module that we'll call Net::Server::POP3. At this time, it doesn't use AUTOLOAD and so probably doesn't break can, but for the sake of argument let's say I was contemplating using AUTOLOAD in the next release. Explain to me why it's important for my module to work with can. What important thing will users of my module need but be lacking if it doesn't?


    ;$;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$;[-1]->();print

      Quite simply, all classes in perl inherit from UNIVERSAL. UNIVERSAL implements can. Your class then has an obligation (IMO of course) to provide a working version of can in its interface. If you break aspects of your base class in your subclass you are doing bad OO and defeating the whole purpose of re-use through inheritance.

      -stvn

        Your class then has an obligation (IMO of course) to provide a working version of can in its interface.

        If we are talking from an OO purity point of view, I would agree. Anything that breaks inheritance is evil and must be avoided.

        OTOH, a lot of people agree that multiple inheritance is needed in some cases, but there is no good way to implement it. How do you handle dimand inheritance? In what order do you call destructors (something that's been an issue on the Squawks of the Parrot blog of late)? There's no good answer, and an awful lot of bad ones.

        Abigail-II gave a great example in this thread of how easy it is to break inheirtance in Perl (one which I hadn't considered before). If you implement your class as a hashref, all your parents and subclasses also must use a hashref. Which might be the most common case, but it isn't the only one.

        Observation: this thread is a case study in why Perl's object system is hacked on.

        ----
        : () { :|:& };:

        Note: All code is untested, unless otherwise stated

        Quite simply, all classes in perl inherit from UNIVERSAL. UNIVERSAL implements can.

        This is an interesting statement. The index of my Camel book (2nd ed) does not list can at all and contains only one entry for UNIVERSAL, which points to page 293, where I find the following quote (emphasis mine):

        If neither a method nor an AUTOLOAD routine is found in @ISA, then one last, desperate try is made for the method (or an AUTOLOAD routine) in the special predefined class called UNIVERSAL. This package does not initially contain any definitions (although see CPAN for some), but you may place your "last-ditch" methods there.

        This not only doesn't support your assertion but seems on the face of it to directly contradict what you were saying. How can UNIVERSAL implement can, and require that all derived objects (i.e., all objects) not break that, if UNIVERSAL does not initially contain any definitions? Further, NOTHING is said here about any obligations that any module has to provide or support any particular method.

        Granted, what I have is not the latest edition, but none of the reviews I have read of the third edition have said anything about the new edition containing important information about fundamental changes to the language that every Perl programmer must know, nor is it advertised that way by the author or by the publisher. It's simply the next edition of the book, no more. I did see some reviews praising the addition of numerous new examples, but nothing that seemed to indicate to me that if I program according to the second edition I'll break things. To be perfectly honest, I've got a lot of things marked in my camel (not least, little tabs on the sides of the pages to mark where certain sections start so I can quickly flip e.g. to the section on special variables), and not wanting to redo all of that right away I was going to wait for the fourth edition, which hopefully will cover Perl6, before upgrading. Perhaps you could quote me just the paragraph of the 3rd edition that explains why every object is required to support the can method.

        update: In the light of morning, the next paragraph seems harsh to me. I didn't mean it that way. I'm not going to edit it out, though, because the reply wouldn't make (as much) sense then. (And chromatic, I recognize your reputation, but you hadn't yet come out to say anything in the thread when I wrote this.)

        I'm interested in hearing about the merits of can, the practical reasons why it's a useful thing for modules to support, even if the module author does not personally use it. I'm somewhat less interested in hearing you just tell me (in so many words) "This is just required". I might accept that coming from someone who is on a first-name basis with Larry's wife, but I am dubious as to what authority you have to make up requirements for all Perl modules to adhere to.


        ;$;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$;[-1]->();print
      I'm assuming that Net::Server::POP3 inherits from some basic Net::Server module that provides basic server functionality. Even if it doesn't, let's say that it does.

      Now, let's say that there is some other server base module, called Net2::Server. It provides a very similar (but not identical) interface, but radically different innards. It's useful for different types of servers.

      Now, let's say that I am using five servers, each an object of a class that either inherits from Net::Server or Net2::Server. I don't know which inherits from which. But, I do know that there is one interface difference I need to be aware of - foo() vs. bar(). So, I can do the following:

      my $method = $server->can('foo') || $server->can('bar'); $server->$method( @args );

      And I guarantee that my code will work, regardless of which baseclass the server inherits from.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        And you've just illustrated where I think that can() can be problematical.

        Despite your claimed guarantee, you didn't guarantee that Net2::Server (or some subclass thereof) won't someday implement a method named foo() that does something radically different from bar(). And then your code will break.

        The problem is that it is as much an abuse of an API to assume that a particular method will never be implemented as it is to use an internal one which may change. Also related is that it is a mistake to assume that what you'd think that foo() should mean will be what someone else will think. This is true even for such obvious values of foo() as a constant named PI. Is that a number or a Unicode character? This related mistake is a meta-problem with can().

        Instead the right way for you to do that is to have some compatibility layer which guarantees that the interface that you want will be supported. That layer could be something as simple as a hash that says that method X is provided by package Y under name Z. Or you can actually implement a class which proxies the methods that you are interested in (incidentally giving you somewhere to put utility methods that one base class has and the other doesn't).

        Now you don't have to worry about the API of Net2::Server changing on you, and you don't need to call can().

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://342804]
Approved by Paladin
Front-paged by Kanji
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (4)
As of 2024-03-19 02:57 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found