leocharre has asked for the wisdom of the Perl Monks concerning the following question:

I've read off and on that when you make an object oriented package, that you should not force poop on people. That it should be an opton, not a default.

So, my first thoughts were ... I guess if my package has method hello(), then there should be a _hello() which is the functional oriented equivalent. Seems silly.. And I have a gut feeling there's various tricks to have a sub act as both a functional oriented, and object oriented subroutine.

I patched this together and it seems to work well. Here's my example:

use Carp; use constant haveyaml => ( eval 'require YAML;' ? 1 : 0 ); my $META_HIDDEN=1; sub META_HIDDEN : lvalue { $META_HIDDEN } my $META_EXT = 'meta'; sub META_EXT : lvalue { $META_EXT } sub set_meta { my $abs_path = shift; if (ref $abs_path eq __PACKAGE__){ $abs_path = $abs_path->abs_path; } my $meta = shift; ref $meta eq 'HASH' or croak('second argument to set_meta() must be a hash ref'); haveyaml or carp 'set_meta() cant be used, ' .'YAML is not installed.' and return; $abs_path=~s/^(.+\/)([^\/]+$)/$1.$2/ if META_HIDDEN; unless( keys %$meta){ unlink $abs_path .'.'.META_EXT; return 1; } YAML::DumpFile($abs_path .'.'.META_EXT,$meta); return 1; }

So this sub can be used as $object->set_meta($hashref) or set_meta($path,$hashref). The way to detect here is to see if the first argument is a ref to the package, or not.

I've looked for examples of having subroutines act as both object oriented and functional oriented code, and I came up kind of empty, maybe I'm not looking right.

Any suggestions how else to do this? How to have a subroutine detect if it's being used as a method or a normal subroutine?

And uhm.. If I can also ask.. Is setting that constant to check for YAML really bad? Or too taxing on the sytem?

update
Using a subroutine as both procedural function and oo method can create problems with inheritance (and complexity). Thanks to the discussion, I can with confidence put this aside and move on. Verdict is nonono. I'll use different subs, some for oo some for procedural.

Replies are listed 'Best First'.
Re: coding a subroutine as both method and regular function
by jdporter (Paladin) on Mar 22, 2007 at 16:38 UTC

    I disagree that it's "almost always a bad idea". You just have to be careful how you do it.

    { package Foo; use Scalar::Util 'blessed'; sub _process_calling_convention { # discard pkg name, if called as class method: @_ && !ref($_[0]) && $_[0] eq __PACKAGE__ and shift; # return object, if called as object method: @_ && (blessed($_[0])||'') eq __PACKAGE__ ? shift : () } sub bar { my $obj = &_process_calling_convention; if ( defined $obj ) { print "called as object method $obj->( @_ )\n"; } else { print "called as function ( @_ )\n"; } } } # main: test: my $x = bless {}, 'Foo'; print "\nplain module function call:\n"; Foo::bar(); Foo::bar("arg"); print "\nclass method call:\n"; Foo->bar(); Foo->bar("arg"); print "\nobject method call:\n"; $x->bar(); $x->bar("arg");

    In this, I've coded based on the presumption that, in the class method call case, you don't care about the class name; it is discarded.

    I should also say that I don't know how well this will hold up under inheritance. You'd probably have to tweak certain things, i.e. rather than string test against __PACKAGE__, test using isa or can.

    A word spoken in Mind will reach its own level, in the objective world, by its own weight
Re: coding a subroutine as both method and regular function
by Anno (Deacon) on Mar 22, 2007 at 18:18 UTC
    Leaving the debate aside for the moment whether this is a good idea in principle, your code as given doesn't support inheritance. When you test to see if you are called as a function or a method, you do
    if ( ref $abs_path eq __PACKAGE__ ) {
    That won't do. If another class inherits from yours, it will have a different name but its objects have every right to call your method. Your code doesn't let them. The problem is similar to calling bless() with only one argument in new().

    Elsewhere in the thread jdporter has mentioned the problem in passing:

    I should also say that I don't know how well this will hold up under inheritance. You'd probably have to tweak certain things, i.e. rather than string test against __PACKAGE__, test using isa or can.

    Using isa( __PACKAGE__) is a possibility. It will make inheritance work, but in the inheritance case it really does too much. The code has been called because the object "is a" __PACKAGE__ object by inheritance, so why check again?

    There is also the possibility that the method was called fully qualified with an object of an arbitrary class, as in $obj->Your::Class::set_meta( ...). In this case, even the isa() test would reject what is a legitimate call. If the programmer thinks the object will support the method, she will probably know what she's doing.

    So I suggest simply testing for object-ness

    if ( Scalar::Util::blessed( $abs_path) ) {
    The discussion also shows how easy it is to break OO capabilites while adapting code so that methods can also be called as ordinary subs. I believe, if you want this capability you'd be better off with a pure OO class, plus an adapter module (possibly AUTOLOADER-driven) that arranges for calls-as-functions.

    Anno

      I get most of what you're saying. Makes sense. Thank you so much for taking time to talk about this.

      It seems using code as both a procedural and an object oriented subroutine, could easily create problems. Perhaps this falls into the scope of 'clever', cute but .. just cute- that's all.

      I'm not going to be using this technique anymore, at least not until I understand better what it is really doing, maybe in a couple of years. Thank you.

Re: coding a subroutine as both method and regular function
by rhesa (Vicar) on Mar 22, 2007 at 16:02 UTC
    I agree with Fletch, it's not a great idea in general. But in case you really need the inspiration, have a look how the venerable CGI module offers both an OO and a procedural interface. The basic pattern there is:
    sub foo { my ($self, @args) = _self_or_default(@_); # continue coding OO-style }
    I'll leave it to you to discover how hairy _self_or_default is.

      In CGI.pm, that is a little bit wild. A lot of these sub call self_or_default()..

      Uhm.. I was gonna dissect how self_or_default() works.. but... ouch. It hurts. CGI.pm v 3.15 line 442:

      sub self_or_default { return @_ if defined($_[0]) && (!ref($_[0])) &&($_[0] eq 'CGI'); unless (defined($_[0]) && (ref($_[0]) eq 'CGI' || UNIVERSAL::isa($_[0],'CGI')) # slightl +y optimized for common case ) { $Q = $CGI::DefaultClass->new unless defined($Q); unshift(@_,$Q); } return wantarray ? @_ : $Q; }

      I mean, I get it.. it sees if the first list element is a ref or not, if it is a ref to 'CGI', it returns the list unchanged. If it is not.. then.. uhm... hmm.

Re: coding a subroutine as both method and regular function
by perrin (Chancellor) on Mar 22, 2007 at 16:05 UTC
    Almost always a bad idea. Now you have to write twice as many tests, since you have to test it both ways. And you can't do anything very interesting on the OO side, since you don't require it.

    If you want an example, look at CGI.pm. It's not an example I would recommend copying. The code is complicated and obscure, partly because of this.

      So, you suggest a package should not offer both procedural and oo methodology? For the general case? Or that it should be solved in another manner, by separating it into different subroutines?

        Yes, I think it's a bad idea to offer both. It just makes more work, more confusion, and more bugs.
Re: coding a subroutine as both method and regular function
by Fletch (Bishop) on Mar 22, 2007 at 15:48 UTC

    Perhaps the reason you came up empty looking for people doing this is because they don't since it's not that great of an idea . . .

    Update: Rather than making this tree deeper than it already is I'll elaborate here.

    As is pointed out below, the suggestion is for the developer to choose between OOP and a procedural interface when designing the API. It's not an exhortation to provide $your->cake() and eat( $it => 'too' ), rather to look at the problem at hand and decide (using the criterion outlined in the following section of that chapter) if OOP fits better or if procedures fit better.

    I've been trying to think of any "big" module which provides both ways simultaneously, and other than CGI I can't. And as has been commented below it's not a pretty picture how it allows this.

    If you want an example of how I'd provide both interfaces (were I made to :) then consider LWP::UserAgent and LWP::Simple. The later provides a procedural interface, while the underlying module is still accessible for those needing more controll.

      I have no doubts at all that you know what you're talking about- That said, could you please give some hints ideas suggestions as to why this is a bad a idea? Really, I just don't want to write bad code.

      And, when Damian Conway (Perl Best Practices) suggests offer oo as 'option' not a default.. this is not what he's talking about?

      So instead, in this example.. I should make a set_meta() and a _set_meta() ? That is, to satisfy that a package offer both procedural and oo api.

        And, when Damian Conway (Perl Best Practices) suggests offer oo as 'option' not a default.. this is not what he's talking about?

        Actually Damian says 'make it a choice, not a default'. I don't think he's referring to the user of the module/class, but the developer of it. So he's suggesting that you don't assume that every module you implement should inherently be a class, but that a non-OO implementation may just as good or a better choice in a given scenario.

        perl -e 'split//,q{john hurl, pest caretaker}and(map{print @_[$_]}(joi +n(q{},map{sprintf(qq{%010u},$_)}(2**2*307*4993,5*101*641*5261,7*59*79 +*36997,13*17*71*45131,3**2*67*89*167*181))=~/\d{2}/g));'
Re: coding a subroutine as both method and regular function
by phaylon (Curate) on Mar 23, 2007 at 13:49 UTC

    As stated above, this is a bad idea for a general interface, and if it's only because it can be confusing to the users of the API.

    That said, there can be usages for this with utility functions. I'd either export a closure that calls my module with some defaults, or use Devel-Callers called_as_method to figure it out.

    But again, better design it in one clear way first that solves all problems. Start with consistence, then add convenience.


    Ordinary morality is for ordinary people. -- Aleister Crowley