http://www.perlmonks.org?node_id=954299

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

Hi Monks,

I want to write a module which provides users both of functional interface and object-oriented one.
# OO interface use Foo; my $o = Foo->new(); $o->method1(); $o->method2(); # Functional interface use Foo qw(method1_foo method2_foo); method1_foo(); method2_foo();
To implement above features, I wrote the following code:
# Foo.pm package Foo; use Exporter 'import'; our @EXPORT_OK = qw(method1_foo method2_foo); sub new { return Foo::Object->new( method1 => sub { shift; method1_foo(@_) }, method2 => sub { shift; method2_foo(@_) }, ); } sub method1_foo { # do method1 } sub method2_foo { # do method2 } package Foo::Object; sub new { my ( $class, %method ) = @_; # adds methods while ( my ( $method, $code_ref ) = each %method ) { next if ref $code_ref ne 'CODE'; my $slot = __PACKAGE__ . "::$method"; { no strict 'refs'; *$slot = $code_ref; } } return bless \do { my $anon_scalar }, $class; } 1;
I'm afraid that my way seems bad practice. I referred to CPAN modules like Object::Prototype. In this case, I think it's not appropriate to use this module. (I can't explain why not appropriate)

Although there are many ways to implement above features, it's difficult to choose one of them. Help me, Monks!

Replies are listed 'Best First'.
Re: Module provides both of functional interface and object-oriented one
by Anonymous Monk on Feb 16, 2012 at 20:19 UTC

    If you forget about exporting your functions, you can try this:

    package Foo; # constructor sub new { my $klass = shift; # maybe take arguments from @_ my $self = bless {}, $klass; return $self; } sub bar { my $self = shift; my ($param1, $param2) = @_; if (ref $self) { # we are an object instance print "hey, I'm an object\n"; } return ($param1 == $param2); } # and how to use it package main; # procedural Foo->bar(6 * 9, 42); # OO my $f = Foo->new(); $f->bar(15 * 17, 255);
      Thanks for your suggestion. I think your code works correctly. Frankly speaking, I'm working on Blosxom::Header inspired by Plack::Util::headers. I'm trying to wrap existent subroutines in an object. Although there are many ways to derive OO interface from procedual one, it's difficult to choose one of them :(

        The basic idea behind the code is: Foo->bar(@_) is really a nice way of saying Foo::bar("Foo", @_) and $f->bar(@_) is Foo::bar($f, @_). After that, ref $self returns false for the string "Foo" (as it is not a reference) but the class name for the object.

        I don't think it's a bad approach at all, but it will break existing subroutine-using code unless you somehow sniff what type the first argument is.

        Since your functional interface already requires passing a reference on every call, I don't actually see why anyone would prefer to use it. Personally, if your module is not in too wide use yet, I would choose to retire it and tell the people depending on your module to change their code.

        On the other hand, there's nothing that forbids you from creating wrappers which you can export. Adding to my previous code:

        @EXPORT_OK = qw/get_bar/; sub get_bar { my $var = shift; # your $blosxom::header hashref bar($var, @_); }
Re: Module provides both of functional interface and object-oriented one
by Anonymous Monk on Feb 16, 2012 at 18:06 UTC
    I concur, it's bad practice. The split over packages is brittle and unnecessary. Done with common modules (untested):
    package Foo; use Moose; use Sub::Exporter -setup => {exports => [qw(procedure1 procedure2)]}; # OO stuff comes here has some_attribute … sub some_method { my ($self, @args) = @_; … } # procedural stuff comes here sub procedure1 { my $self = __PACKAGE__->new; # if needed my @args = @_; … } sub procedure2 { my $self = __PACKAGE__->new; # if needed my @args = @_; … } 1;
    Usage from the main code:
    use Foo qw(procedure1); procedure1(foo bar quux); my $f = Foo->new(some_attribute => baz); $f->some_method;
      Thanks for your suggestion. I feel it's modern to use Moose. Actually, I want to use my module in CGI environment, and so I think it's heavy to load Moose. In addition, I'm a Perl beginner. I want to know how to implement above features in an old-fashioned way.