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

I'm trying out an interesting practice.
When I code OO, first I code 'would be methods' as functions only, in a separate package. And use tests to check them out.
Later I can code a OO interface, which makes use of those functions.

Example.

Let's say I have Cleaning.pm
I have Cleaning::Functional::sweep() and Cleaning::Functional::vacuum() subroutines that can be exported. These take a few parameters and options via a hash ref, room of the house, maybe the kind of floor, etc.

I also have an OO interface, via which I can clean a whole house without telling each function every freaking parameter each time (amongst other OO pros). So I have Cleaning::OO::new(), via which I can have $object->vacuum(), $object->sweep(), etc. (I want to emphazise that the OO interface is not a rewrite of the functions!)

Cleaning::OO, imports Cleaning::Functional subs.

The following is imaginary code (this is a lose example):

package Module::Functional; # functional # the more complex operations and logic would happen here sub clean_to_level { my ($cleanliness_now, $target_cleanliness) = @_; while( $cleanliness_now < $target_cleanliness ){ $cleanliness_now++; } return $cleanliness_now; } 1; package Module::OO; use Module::Functional; # oo version # this package would in great part be an interface to the other packag +e. sub clean_to_level { my ($self,$target_cleanliness) = @_; $target_cleanliness ||= $self->default_target_cleanliness; return Module::Functional::clean_to_level( $self->cleanliness_now, $target_cleanliness, ); }
I think this has been helping me test better, and code better OO.

Here's my questioning.

Having Module::Functional::run() and Module::OO::run() looks creepy (?). Giving either (style/programming philosophy/methodology?) the prime namespace real estate over the project seems biased- and misleading! As if it hints that you should use one over the other.- And then, I really want the functional run() and the oo $o->run() to have the same sub name, obviously slightly different doccumentation- and if they reside in the same package... This would be like.. CGI.pm (?)

(CGI.pm for example- you can use the calls as subs or methods.
I've looked through CGI.pm- and it makes me feel like a drunken sailor at a strip joint. Way over my head.)

I want the code to be able to be used as both functional or OO. I foresee myself and others wishing to use the code- to want to use both on occassion. The OO stuff is much more friendly to use in a larger program, the functional is much more easy to recycle ( my personal feelings about what I code ).

So far, I've been alright by using separate packages to accomplish this.
What about using one package like in CGI.pm? That can be used both ways, if you call import tags, it does so.. if you call new, you're in oo mode.. etc.

Replies are listed 'Best First'.
Re: Is it ok to mix functional and oo programming in one package?
by perrin (Chancellor) on Oct 18, 2007 at 18:51 UTC

    No. Next question.

    Seriously, CGI.pm is a terrible example of interface design. The OO interface is constantly breaking because of the need to support the non-OO one, which relies on lots of globals and requires crazy stunts in long-running environments like mod_perl and FastCGI.

    Here are some reasons to avoid mxing:

    • Inevitably means more code to maintain, test, and change. Your code would require changes in multiple places if you modify the parameters a function takes.
    • Ruins OO features like inheritance.
    • You didn't actually do this in your example, but having some subs that are called as functions and some that are called as class methods in the same package inevitably leads to confusion and bugs caused by mistakenly calling one the wrong way.
Re: Is it ok to mix functional and oo programming in one package?
by gamache (Friar) on Oct 18, 2007 at 18:12 UTC
    (I suspect you meant to say "imperative programming" rather than "functional programming", so I'm gonna run with that assumption.) There are plenty of examples of this behavior. (Forgive me if I don't name any.) I don't think it has to end up messy or troublesome at all. Here's a short example of one such setup.

    In Foo.pm:

    #!/usr/bin/perl use strict; use warnings; package Foo; sub new { my $class = shift; bless { name => 'foo' }, ref($class)||$class; } sub bar { my $ref = ref $_[0]; if ($ref eq __PACKAGE__) { ## OO method call oo_bar (@_); } elsif ($ref eq 'HASH') { ## imperative subroutine call with hashref as first element im_bar (@_); } } sub oo_bar { my $self = shift; print "oo_bar got object named ", $self->{name}, "\n"; } sub im_bar { my $hr = shift; print "im_bar got hashref with name ", $hr->{name}, "\n"; } 1;
    In im-oo-test.pl:
    #!/usr/bin/perl use strict; use warnings; use Foo; my $hr = { name => 'hashref' }; my $obj = new Foo; ## OO method call $obj->bar; ## imperative call, but since $obj is a blessed object, ## this is equivalent to the OO call Foo::bar($obj); ## imperative call Foo::bar($hr);
    There's a zillion ways to proceed from this skeleton. You could use a naming scheme like Module::OO::sub() rather than oo_sub(), you could write one subroutine to handle both types of calls, you can make the ref $_[0] business a little cleaner with use UNIVERSAL 'isa'... you can even ignore whether a hashref is an object, provided it has the right keys and values in it.

    I would consider it good programming practice to put maximum flexibility into how your subroutines may be called, as long as this flexibility doesn't introduce too much complexity on the backend. That's your call.

      There are some gotchas here.

      bless { name => 'foo' }, ref($class)||$class;

      The ref dance is completely unnecessary. Unless you're implementing a prototype-based OO system (and the rest of the constructor suggests that you're not), drop it.

      my $ref = ref $_[0]; if ($ref eq __PACKAGE__) {

      This just broke inheritance. If you really need to do this check, use Scalar::Util's blessed() or reftype().

      my $obj = new Foo;

      This syntax introduces weird parser ambiguities that often depend on the order of compilation. Stick with Foo->new().

      Foo::bar($obj);

      This breaks inheritance.

      ... you can make the ref $_[0] business a little cleaner with use UNIVERSAL 'isa'...

      That will break delegation and cognates and has the possibility of breaking inheritance. It may break overloading as well.

        my $obj = new Foo;

        This syntax introduces weird parser ambiguities that often depend on the order of compilation. Stick with Foo->new().

        According to the Camel Book beside me, method invocation using the METHOD CLASS_OR_INSTANCE LIST form is exactly equivalent to CLASS_OR_INSTANCE->METHOD (LIST) form.
Re: Is it ok to mix functional and oo programming in one package?
by doom (Deacon) on Oct 18, 2007 at 18:53 UTC
    Well... I can't say I'm enthusiastic about your approach here. Objects are slow enough without turning every method into a wrapper around another sub call; and I don't see how a proceedural module is any eaisier to write tests for than an object-oriented one (though, to make testing a little eaiser, I'm inclined to design my object classes so you can get a bare-bones object with a simple "new" without arguments).

    A typical test for me is something like:

    { my $test_name = "rect_probe"; my $bf = Image::BoxFinder->new(); my $spot = $bf->rect_probe; is_deeply( $spot, [ 0, 0 ], "Testing $test_name: found rectangle"); }
    The extra line to create the object isn't a lot of overhead; and myself I just think of the method call ($bf->) as just a short alias for Image::BoxFinder::.

    On the subject of mixing and matching methods and proceedures in one module: this makes some people queasy, and I have mild reservations about it myself, though they're only mild.

    On the subject of modules with both an object and a proceedural interface, myself I think they're over-kill. Remember: if you put in two totally different interfaces, you're going to have to document both of them.

Re: Is it ok to mix functional and oo programming in one package?
by erroneousBollock (Curate) on Oct 18, 2007 at 18:06 UTC
    Are you sure you mean to say 'functional'? I'd say that's more like 'pure imperative' (where pure means non object-oriented).

    -David

Re: Is it ok to mix functional and oo programming in one package?
by TGI (Parson) on Oct 18, 2007 at 21:55 UTC

    I'd avoid mixing the two models.

    If I really needed an imperative interface and an OO interface, I'd be inclined to make an imperative wrapper for an OO library. I'm not 100% sure about how I'd handle the details. I think you'd have to do some autoloading of subroutines.

    For example, if I call ImpWrapper::foo(), then ImpWrapper::AUTOLOAD would need to check to see if its global object has a method foo(). If so, we would create ImpWrapper::foo() and have it delegate to  $iw_global_obj->foo().

    My AUTOLOAD-fu is not really that good, so to be sure of this approach, I'd have to spend more time than I have investigating it. But it seems like it should work pretty well, as long as you don't do anything crazy, like reblessing your wrapper's object into a different class.

    I can imagine a general ImpWrap module that allows you to do something like:

    package Imperative::TheObject; use ImpWrap TheObject => ( name => foo, address => '123 A Street', foo => 'bar', );

    Then ImpWrap would create a TheObject object, and put it into the Imperative::TheObject namespace. ImpWrap would also insert an appropriate AUTOLOAD routine.

    If I ever thought I might need such a thing, it might be an interesting project.


    TGI says moo

      Here's a first pass at what I was talking about above:

      use strict; use warnings; package ImpWrap; sub import { my $this = shift; my $class = shift; my $method = shift; my $caller = (caller)[0]; warn "Importing $class/$method from $caller\n"; my $obj = $class->$method(@_); { no strict 'refs'; my $autoload = sub { our $AUTOLOAD; my ($al_method) = $AUTOLOAD =~ /::([^:]*)$/g; if ( $class->can( $al_method ) ) { *{ $AUTOLOAD } = sub { $obj->$al_method( @_ ); }; goto &{$AUTOLOAD}; } else { require Carp; Carp::croak "Undefined subroutine $AUTOLOAD"; } }; # Insert AUTOLOAD function; warn "Setting autoload\n"; *{ "$caller\::AUTOLOAD" } = $autoload; } } package Fuz; use Data::Dumper; sub bizzle { my $self = shift; print "bizzle: $self\n"; print Dumper $self; } sub new { my $class = shift; bless {@_}, $class; } package Foo; our @ISA = ('Fuz'); package IW::Foo; import ImpWrap 'Foo', 'new', (1..4); package IW::Fuz; import ImpWrap 'Fuz', 'new', (11..20); package main; IW::Foo::bizzle(); IW::Fuz::bizzle(); IW::Foo::Grazzle();

      I'm sure there are lots of things this doesn't do quite right yet. I know it doesn't handle autoloads in the wrapped object class properly. Or inherited autoloads.

      The main thing is that it seems to work for basic usage. I create an AUTOLOAD function for the wrapper package that is a closure around the wrapped object. As a result, any class methods will be called as instance methods. I could make it possible to define a list of class methods to call by name, methods to skip, and so forth, by adding appropriate argument handling to the import subroutine.

      Anyhow, this is just a start. I'd like to get proper handling of AUTOLOADs in wrapped objects (with inheritance) and any other gotchas I can think of handled. Any suggestions are welcome.


      TGI says moo

Re: Is it ok to mix functional and oo programming in one package?
by aufflick (Deacon) on Oct 19, 2007 at 09:44 UTC
    I'm not convinced it's a good idea, but you could keep your separate packages, and make a third wrapper package with an autoload that dispatched to one or the other based on inspecting $_[0] (ie is it a ref of an appropriate class?)

    Just because it can be done doesn't mean it should be though!

Re: Is it ok to mix functional and oo programming in one package?
by bennymack (Pilgrim) on Oct 19, 2007 at 16:28 UTC

    I've recently adopted a coding style that results in code that can be executed both purely procedurally or in an object oriented fashion. I find that as long as I consistently stick to the style, my code is much more flexible for both internal and external use.

    In a nutshell, I alwas add the line my( $self, %args ) = @_; *unless* it's an new *or* import. Then I substitute $self with $class.

    The resulting subroutines end up looking like this:

    sub do_something { my( $self, %args ) = @_; my( $scalar ) = $args{scalar} || eval { $self->{scalar} } || $self->SCALAR; my( @list ) = ( eval { @{ $args{list} } or 1 } ) ? @{ $args{list} : $self->LIST; }

    Then calling code can choose to pass in parameters it wishes to override, or define a subclass that overrides the defaults (such as SCALAR and LIST.)

    This approach has worked well for me so far. It supports both OO and procedural. It doesn't break inheritance, in fact it uses inheritance to reduce the number of arguments necessary if changing the defaults makes sense.

Re: Is it ok to mix functional and oo programming in one package?
by eric256 (Parson) on Oct 19, 2007 at 21:47 UTC

    If you don't need to store all the parameters why would you want to make it an object? And if you always need the parameters why would you want to make it functional?

    I've never had that issue, but i've never had code that could go either way. If I did, then I think i would make it OO and then users who wanted to use it without storing parameters in the object could build an object with no parameters and call its methods passing all information.

    my $house = new House(width=>100, height => 100); $house->sweep; # or my $blank = new House; $blank->sweep(width=>100, height => 100); # or even House->new->sweep(width=>100, height => 100);

    Either way make sure that your new isn't an expensive method and it shouldn't matter alot.


    ___________
    Eric Hodges