Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things

Re: Re: Re: Re: Re: Why breaking can() is acceptable

by stvn (Monsignor)
on Apr 06, 2004 at 15:23 UTC ( #342959=note: print w/replies, xml ) Need Help??

in reply to Re: Re: Re: Re: Why breaking can() is acceptable
in thread Why breaking can() is acceptable

Shell is a perfect example of how AUTOLOAD should be used. And looking over it, I cannot see a good way to write can for it since it really just executes on the shell anything you give it (whoa security risk!). However if you specified your available shell commands, then you could make a reasonable can with no trouble at all. Even Larry seemed to think maybe they were playing with fire (quoted from here)

That's maybe too gonzo. It actually exports an AUTOLOAD to the current package (and uncovered a bug in Beta 3, by the way). Maybe the usual usage should be
use Shell qw(echo cat ps cp);
If we were to follow Larry's suggestion, the implementation of can becomes very simple to implement as we know what methods/functions are being created/AUTOLOADed.
Note that when I've chosen to use AUTOLOAD in the past, it tends to be short.
I have used AUTOLOAD on occasion too, but only when I really need to (and just as with you) it tends to be short. An example is in Class::Trait here. I am not sure if my usage breaks can, but you can be sure I will test it in the next release.

As for Class::FlyweightWrapper, here is your can

package Class::FlyweightWrapper; $VERSION = 0.01; use strict; use Carp; my $BASE_PACKAGE = <<'EOT'; # line 1 "'Flyweight wrapper PUBLIC for PRIVATE'" package PUBLIC; my %object = qw(PUBLIC PRIVATE); sub DESTROY { delete $object{$_[0]}; } sub AUTOLOAD { my $meth = $PUBLIC::AUTOLOAD; $meth =~ s/.*:://; my $self = $object{shift(@_)}; return $self->$meth(@_); } sub can { my $self = $object{shift(@_)}; my ($method_name) = @_; return $self->can($method_name); } # Make sure things cleanup properly END { %object = (); } EOT my $BASIC_CONSTRUCTOR = <<'EOT'; sub CONSTRUCTOR { my $self = bless \ my $scalar, "PUBLIC"; my $class = shift; $object{$self} = $object{$class}->CONSTRUCTOR(@_); $self; } EOT sub import { shift; # Not interested in my package my $public = shift || croak("Usage: use Class::FlyweightWrapper 'Public::Package';"); my $private = caller(); my @constructors = @_ ? @_ : 'new'; my $template = $BASE_PACKAGE; $template =~ s/PUBLIC/$public/g; $template =~ s/PRIVATE/$private/g; foreach (@constructors) { my $piece = $BASIC_CONSTRUCTOR; $piece =~ s/CONSTRUCTOR/$_/g; $piece =~ s/PUBLIC/$public/g; $piece =~ s/PRIVATE/$private/g; $template .= $piece; } eval $template; if ($@) { confess("Template\n$template\ngave error $@"); } } 1;
Although I could not test it under inheritance because I could not get my class to be inheritied
#!/usr/bin/perl package Test::Private; use Class::FlyweightWrapper "Test::Public"; sub new { return bless {}, ref($_[0]) || $_[0]; } sub helloWorld { print "Hello World!\n"; } package DerivedTest; @DerivedTest::ISA = qw(Test::Public); package main; my $test = Test::Public->new(); $test->helloWorld(); print (($test->can("helloWorld")) ? "we can\n" : "we can't\n"); my $test2 = DerivedTest->new(); # <<< dies here $test2->helloWorld(); print (($test2->can("helloWorld")) ? "we can\n" : "we can't\n"); 1;
The output this produces is:
Hello World! we can Can't call method "new" on an undefined value at 'Flyweight wrapper Te +st::Public for Test::Private' line 24.

Regarding Re (tilly) 1: Nested Classes, a can like addition to your object system is easily enough to implement since you have all the methods defined in a hash already. As this is an example of creating your own object system, I wont bother trying to think of how a version of can might fair in other situations (inheritance, multiple-inheritance, etc), since creating your own object system is hairy enough, and your implementation is incomplete.

As for Re (tilly) 1: Reverse Inheritance Irritance, you are just dispatching your calls to the parent object, so it makes sense you can dispatch requests to can as well. Like I said, it may not be easy, but I think in many cases it is worth it, you may not agree with that, and that is your choice.


Replies are listed 'Best First'.
Re: Re: Re: Re: Re: Re: Why breaking can() is acceptable
by tilly (Archbishop) on Apr 06, 2004 at 16:22 UTC

    On Class::Trait. Not a bug, but anyone mixing SUPER:: and multiple inheritance is simply asking for problems. If you want to support multiple inheritance, then you would do well to look at whether you are called from NEXT:: as well. (I'm actually not sure what Class::Trait is supposed to be for, but that is a different issue.) I'm also amused that you apparently find it more acceptable for a module author to say, "I won't support multiple inheritance," than "I won't support can()."

    On Class::FlyweightWrapper, the subroutine that you return is the one that is meant to be given the private object, not the public one. Since the caller isn't supposed to have the private object, that should be useless. Furthermore the reason why you can't inherit is because the implementation (including inheritance) is supposed to be done in the private hierarchy with public hooks defined where you decide.

    About Nested Classes, I'd prefer to say that the system is basic rather than incomplete. It does what it claims to do. That thing is useful as it stands. Opinions on what belongs in an object system vary widely, so you're never going to have everything that everyone thinks should be there (unless you've implemented stuff that a lot of people think shouldn't be there as well).

    On the Reverse Inheritance Irritance, if I override can() there and call SUPER::can() first (to handle the case where I have implemented the method elsewhere), then if a parent class has overridden can() for their AUTOLOAD, can will return the parent's AUTOLOAD, while calling the method is going to call my AUTOLOAD. The truth value of can() is right, but the subroutine returned is wrong. Calling UNIVERSAL::can alleviates the problem somewhat, unless someone has overridden UNIVERSAL::can. (Possibly because they want to follow the scheme that I had in the root node which makes AUTOLOAD and multiple inheritance able to cooperate seamlessly.)

      On Class::FlyweightWrapper, the subroutine that you return is the one that is meant to be given the private object, not the public one. Since the caller isn't supposed to have the private object, that should be useless.

      Wouldn't the following be sufficient?

      package Class::FlyweightWrapper; ... sub can { my $self = $object{shift(@_)}; my ($method_name) = @_; my $sub = $self->can($method_name) or return; sub { $object{shift(@_)}->$method_name( @_ ) } }

        You've not supported calling can() on the package. You've also not supported the correct behaviour of can() on methods that actually were defined directly in the PUBLIC class. Such as the constructor, or (now) can(). (A list to which which the user of the module could choose to add.)

        However you're right that someone who wanted to write the AUTOLOAD and then tried to add can() after the fact likely would implement just that and not notice the omissions. (Nor would the testing mantra help - you can't test cases that you didn't think of. And testing AUTOLOAD is harder than it looks because of how much behaviour even a short AUTOLOAD provides to write tests for.) Which illustrates by example my point that even when people try, they'll repeatedly get it wrong.

      RE: Class::Trait

      Class::Trait is an implementation of a research concept. The documentation on CPAN refers to several papers on it. The idea is to be able to implement "incomplete" mini-classes that can be "mixed in" to regular classes all while in a single inheritance paradigm. Its kind of like multiple-inheritance, mix-ins and deffered classes all mixed together with some strict rules on their behaviors/interactions. Where it differs most is in the concept of flattening. Trait methods are not inherited but instead flattened into the class which uses them.

      So, while you are correct in regards to SUPER not being enough in the presence of multiple inheritance, Traits are specifically designed (not by me, but the researchers) to be used in a single-inheritance world. In addition traits themselves have a set of restrictions such as not being able to have state, which I extended for perl to disallow DESTROY methods (since you can't have state, you have no need for DESTORY). Also traits are only allowed to call the immediate object or its SUPER class. All this is explained much better in the papers.

      I actually do not believe that it is okay to break multiple inheritance, in normal OO modules. Class::Traits is not a normal OO module, matter of fact its not an OO module at all, but more a pragma in the sense that it does all its work in the compilation stages. Traits is a very specific idea, and really still a research idea too, not really something meant for serious use yet.

      RE: Class::FlyweightWrapper

      On Class::FlyweightWrapper, the subroutine that you return is the one that is meant to be given the private object, not the public one. Since the caller isn't supposed to have the private object, that should be useless.
      Quite true, although this brings up the whole issue of privacy and access control, which is another debate unto itself. I might argue that since the real class is already hidden from the user, they have no easy way of knowing if the method i return is from the public or private version (although they could find it out if they were really curious). But if you do not want to allow direct access to your private object, then we can alter can to do this:
      sub can {     my $self = $object{shift(@_)};     my ($method_name) = @_;     my $method = $self->can($method_name); return unless $method; return sub { $method->(@_) }; }
      Of course this implementation is not perfect either, as it will report as ANON in the call stack. But then again, normal use will also expose the use of the private object on the call stack. If you have more specifics of the behavior you intend Class::FlyweightWrapper to implement, we can discuss it and I'm sure find a solution, but right now you have me shooting in the dark. My point really is that it is possible, given a well thought out specification regarding the behavior, one can make it work.
      Furthermore the reason why you can't inherit is because the implementation (including inheritance) is supposed to be done in the private hierarchy with public hooks defined where you decide.
      Ah, this was not really alluded too in the documentation. But I would argue that I should be able to inherit from your public version as well. I mean what is the use of encapsulation when in order to inherit you are forced to break it?

      Clearly Class::FlyweightWrapper, while a nice chunk of code, is not completly fleshed out to all the corners. This is not to say its bad, just that you apparently have done anything with it since 8/10/01 and its still version 0.01. If you want to work on it again and take it to the next level, I would be happy to collaborate on it with you, and then we can make sure it works with both can and AUTOLOAD ;-)

      I am refraining from further comment on the other 2 implementations as they too are not really at a level where real discussion of features and implementation can take place.

      My guess is that we are boring the other monks with this back and forth. I recommend we drop it (unless you wanted to get the last word in, in which case go right ahead). In the end, no matter what, I will insist that every effort should be made to make can work with AUTOLOAD, and you will likely disagree. But this is okay, diversity of opinion is one of the great things about perlmonks, as well as just another aspect of TIMTOWTDI.

        On traits, I have seen that lots of people have lots of verbiage about it, but I've skipped that because my provisional opinion at Re: Re: very simple per-object mixins about mixins is also my provisional opinion on traits. They are a bad idea unless they are done a lot, with a relatively few examples being done. Then they become good.

        On the version of can that you provided, I assume that you had a typo, you wanted the return to be return sub { $self->$method(@_) }; In which case your solution becomes the same as the one that simonm came up with at Re: Re: Re: Re: Re: Re: Re: Why breaking can() is acceptable, and my reply at Re: Re: Re: Re: Re: Re: Re: Re: Why breaking can() is acceptable. My point that I'd expect people to come up with this answer and not noticed its issues has been strengthened by the fact that that has now happened twice in a row.

        If you want to take Class::FlyweightWrapper to the next level, be my guest. I'm not particularly interested in it because I'm not using it. It wasn't hard to implement the first time, and the fact that I'm not using it means that I don't have any intuition on where people will have issues with it.

        On the other 2 implementations. While neither is aimed for widespread use, both are functional enough to be used in a local project. I wouldn't be surprised if a lot of real production code that uses AUTOLOAD is at a similar level, excepting the fact that real code has become longer because people have kept on adding stuff to it.

        About diversity, that is a good thing about Perlmonks. You're right about the likely outcome, but hopefully this thread gives you some perspective about why at least some people won't pay as much attention as you would like to your insisting.

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (3)
As of 2023-03-24 02:32 GMT
Find Nodes?
    Voting Booth?
    Which type of climate do you prefer to live in?

    Results (60 votes). Check out past polls.