I'm interested in opinions on the use of FP (functional-Perl, ie: non-OO) routines exposed to consumers of an otherwise OO-module. Given that this is almost completely a style preference, I've chosen to Meditate on it. See the concept code below for an idea what's involved. While the examples are terse, the idea is to expose package-configs, possibly defaults to constructor use, or any package-wide tuning a module may support.

As I see it, there are 3 potential options:

  1. Use FP routines, and document public API use as Ex::Mod::conf(key=>'value')
  2. Use OO-style syntax, and document public API use as Ex::Mod->conf(key=>'value')
  3. Avoid such use all-together, making the caller always list every option desired.

#1 above is what my code samples use, and seems to best represent what the routine does. On the other hand, callers now need to remember if they're invoking an FP routine verses a method/constructor.

#2 is basically the same, but lets consumers be lazy and use the infix operator in all cases. This might be written in the module as:

sub conf { shift; # throw away unused class. # .. do real work here }

#3 would make callers always provide every default option (or not have any say in configuring other package-level adjustments.) Perhaps the callers re-use lengthy/common constructor arguments like so:

my %re_use = (name => 'Alice'); Ex::Mod->new( override=>'whatever', %re_use );
I'm not really fond of making callers do extra work if it's considered likely that use of a particular library might wish to set sensible defaults throughout use of the module. Further, this means package-config (not used by constructors) could only be adjusted by reaching into something like $Ex::Mod::DefaultTimeout to enact changes, which also feels messy.

Below is the example consumer, and the example Ex::Mod class:

consumer.pl

use strict; use warnings; use lib '.'; require Ex::Mod; Ex::mod::conf( user => 'Alice' ); my $a = Ex::mod->new(); # Presumably more constructor use wanting user=>'Alice' goes here.. my $b = Ex::mod->new( user=>'Bob'); printf "User a is: %s\n", $a->whoami(); printf "User b is: %s\n", $b->whoami();

Ex/Mod.pm

package Ex::mod; use strict; use warnings; # package-scoped config. my %conf = ( user => undef, ); # config routine. NB: FP! # <class>::conf( 'key' => 'value' ); sub conf { my %c = ( @_ ); for (keys %c) { $conf{$_} = $c{$_} if exists $conf{$_}; } } # new(), the constructor. sub new { my $class = shift; $class = ref($class) || $class; # subclass boilerplate. my %opts = ( @_ ); my $self = { user => $opts{user} // $conf{user} // '', }; return bless $self, $class; } # whoami() - method to say who this object's user is: sub whoami { my $self = shift; return $self->{user} // undef; }

Replies are listed 'Best First'.
Re: OO APIs and FP interfaces
by Your Mother (Archbishop) on Dec 04, 2015 at 02:13 UTC

    Interesting discussion but I think you have confused the term functional with procedural. I know because I did it too. :P

Re: OO APIs and FP interfaces (storage scope)
by tye (Sage) on Dec 04, 2015 at 15:27 UTC

    The problem with your approach is that you are storing the "config" in a global scope which means that users of this module in one part of your application can stomp on users of this module in some other part of your application.

    A much better approach is to let the user of the module decide where to store any particular "config".

    my $conf = Ex::mod->conf(user=>'Alice'); my $a = $conf->new();

    And that is even trivial to achieve by allowing creation of incomplete objects.

    sub new { my( $conf, %opts ) = @_; my $class = ref($conf) || $conf; # subclass boilerplate. $conf = {} if ! ref $conf; my $self = { user => $opts{user} // $conf->{user} // '', }; return bless $self, $class; }

    Depending on your use case, you might have separate c'tors for partial objects that can only serve as "config" vs. fully-initialized objects. For example, a useful object might require a connection to a service, a step that only happens when new() is called but you can pre-set default values in an object by calling the conf() class method.

    In your simple example, you can just use the new() method to also be your "conf" method.

    One minor down side is that you'll likely need to have methods that do work protect themselves from being called on incomplete objects. But that is often quite simple and obvious:

    sub send_request { my( $self, ... ) = @_; croak "Can't send_request via incomplete object\n" if ! $self->{socket};

    - tye        

      Great alternative, thanks! I see your point about additional work in the module to verify (and if necessary protect) correct operation, yet this seems to be a nice hybrid between giving a caller the ability to prepare a partially configured template and the flexibility to instantiate usable objects out of the templates. This is an excellent extension of the design options I originally proposed.

      This also has the benefit of being subclass-friendly, which choroba noted earlier as a flaw in package-scoped lexicals.

Re: OO APIs and FP interfaces
by choroba (Cardinal) on Dec 04, 2015 at 13:45 UTC
    How should I subclass Ex::Mod (or is it Ex::mod?) by adding a new parameter, e.g. perm? %conf is lexical in the package and the conf sub only operates on its keys that I can't change.
    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,