Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister

Re: What is this can() and why is breaking it (un)acceptable?

by dragonchild (Archbishop)
on Apr 06, 2004 at 14:56 UTC ( #342948=note: print w/replies, xml ) Need Help??

in reply to What is this can() and why is breaking it (un)acceptable?
in thread Why breaking can() is acceptable

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

Replies are listed 'Best First'.
Re: Re: What is this can() and why is breaking it (un)acceptable?
by tilly (Archbishop) on Apr 06, 2004 at 19:19 UTC
    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().

      So, to summarize what it is I think you're getting at:

      can() doesn't really serve any useful purpose.

      1. It doesn't really provide usefulness as an interface validator (because interfaces change and because of AUTOLOAD)
      2. Its secondary function of providing a coderef doesn't help, because of AUTOLOAD and because of the questionable use of this

      So, why does it exist?

      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

        What I want to get at depends on your programming style. Let me first give some perspective.

        If you are good about having really clear function names, then in practice nobody is actually going to name a function something that will conflict with the name that you look for. In that case, if can() finds the method, it is a reasonable assumption that the method will, in fact, do what you expect it to. Furthermore if you are good about not using AUTOLOAD, or else using AUTOLOAD and overriding can() and being aware of where your override works, then it is reasonable to assume that can() does what it should do.

        In that case there are lots of interesting design patterns that use can() for reflection. Which is why enough people consider it useful that it got put in the core.

        If, however, either of the assumptions is wrong, then its utility drops rapidly. It is fine in cases where you check that it works, but it will be a fragile layer. (Depending on your perspective you can blame the fragility on can() itself, or on the practices that made can() fragile. It doesn't really matter - the fragility is there.)

        Furthermore there are other styles of programming in which you are very unlikely to want to reach for can() at all. Therefore an erosion of can() is going to not matter to you.

        If you're in the first camp, people who want to reach for can() whose own personal style is going to make it work out for you, then people who casually break can() are going to frustrate you. I would like you to understand the perspective of people who are going to break can(), and be aware of why it is unrealistic to expect them to generally improve and start working like you want them to work.

        If you're in the second camp, people who are likely to accidentally break can(), I want you to understand what the issue is, why it becomes an issue, and have enough perspective to more deliberately make the decision about whether or not YOU will choose to support can() in any particular scenario.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://342948]
and the questions are moot...

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (2)
As of 2018-07-18 22:46 GMT
Find Nodes?
    Voting Booth?
    It has been suggested to rename Perl 6 in order to boost its marketing potential. Which name would you prefer?

    Results (397 votes). Check out past polls.