Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

module w/ object-oriented and functional interfaces: best practices?

by temporal (Pilgrim)
on Sep 21, 2012 at 16:27 UTC ( [id://994939]=perlquestion: print w/replies, xml ) Need Help??

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

When I write modules for Perl they typically follow a functional style of programming. I learned Perl from a functional point of view and I feel like Perl best lends itself to this paradigm. Certainly not saying that Perl can't do OO (I have written some OO code in Perl before, enough to know the basics), but just saying this now so that you know where I'm coming from.

That said, I have been running into situations where I want to access some modules in a more OO style. Or extend them so that I can leverage some of the benefits of OO. I'd like to keep all their functional components in place but add on this OO interface as well.

Dug up this post: Module provides both of functional interface and object-oriented one, which was a good read but didn't seem to come to any real conclusions. I know there are some great modules on CPAN that implement this sort of dual interface. The ones that I've code dived look like they go ahead and write separate functional routines which can be exported should the user want a functional interface.

Since this is primarily retrofitting some already written modules I'd rather not have to rewrite all of my functions. So least effort would be a nice plus but it doesn't outweigh keeping things 'best practice'.

My initial thought was to add in OO and then my already written functions can determine intent based on their arguments. My second thought was to just spend a bit more time on this and make everything primarily OO and fake the functional side by having them create self-objects every time they are called functionally. Probably a significant performance hit there for functional implementers.

One final note: I am aware of Moose et al. Seem very handy, but I have not taken the time to learn them. I want to avoid using these until I have a solid grasp of how to implement OO in Perl by hand. So in a way I am approaching this as a bit of a learning experience as well. Besides, I am already fluent in other OO languages that look and feel like what Moose-ish modules do to Perl.

Bit long-winded but I hope that I got my situation and questions across. Any ideas/recommendations/links/rants on best practices for implementing OO + functional interfaces for modules?

Replies are listed 'Best First'.
Re: module w/ object-oriented and functional interfaces: best practices?
by chromatic (Archbishop) on Sep 21, 2012 at 18:41 UTC
    I know there are some great modules on CPAN that implement this sort of dual interface.

    Which do you have in mind? The only one I can think of is CGI, and it's either the worst example of a dual interface or the best, in that it's slow, complicated, potentially very buggy, and difficult to maintain.

    For me the important question is whether an operation needs to encapsulate some kind of state. If so, I use an object interface. If not, I use a procedural interface.

    I want to avoid using these until I have a solid grasp of how to implement OO in Perl by hand.

    That sounds backwards to me. I've written my own regular expression engine, but I wouldn't suggest someone take a class on automata before performing simple matches.

      Okay, you've got me there chromatic. I am definitely not any sort of an authority on what constitutes a "great" CPAN module ;)

      But I do see this sort of thing a lot... most recently when I was fiddling with upgrading my config file loading with Config::General:

      # the OOP way use Config::General; $conf = new Config::General("rcfile"); my %config = $conf->getall; # the procedural way use Config::General qw(ParseConfig SaveConfig SaveConfigString); my %config = ParseConfig("rcfile");

      I'd like my modules to be able to do this in the most intelligent way possible! Which, as it turns out, requires intelligently considering how your module is being used and your own requirements. Curses, I hate intelligently considering things.

      Anyway, as for going about it 'backwards', you're probably right. I take a learn-first, apply-later approach to things. Admittedly not that efficient (I could just use Moose and forget about all the small stuff involved in Perl OO). But I'd rather investigate what's really going on with the small pieces first and then jump up to using the feature-rich tools other people have built from them.

      I'm here to enjoy myself tinkering with the ins and outs, the pieces and possibilities first and get work done (close) second. Don't tell my boss =P

      But I think I come out ahead in the long run in terms of getting things done. We're getting to that old 'bottom-up' vs 'top-down' learning style argument, haha.

      Strange things are afoot at the Circle-K.

Re: module w/ object-oriented and functional interfaces: best practices?
by BrowserUk (Patriarch) on Sep 21, 2012 at 17:41 UTC

    Kinda hard to advise seeing as you don't want to re-write your procedural (functional has other connotations), stuff, but haven't shown us what it looks like.

    At the simplest level, a method is just a function that has a hashref (or other ref) pointing to the data it is to operate on, as its first argument.

    If your procedural functions were written (or could be easily modified) to take a hashref containing the data they operate on, then making the module "OO" could be as simple as adding a constructor sub that builds a hash from its inputs, blesses a reference to it and returns it.

    That way, all your existing subs become methods also, if the reference to the data is blessed,o with no further work required.

    It probably wouldn't be "good OO"; but it would satisfy the requirement of having both styles of interface.

    But then the question becomes; why do you want two styles of interface?

    Individual problems/libraries generally lend themselves best to one or the other. Having both is often no more than paying lip service to peoples wont to have things work in one particular way.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

    RIP Neil Armstrong

Re: module w/ object-oriented and functional interfaces: best practices? (2 pkgs)
by tye (Sage) on Sep 21, 2012 at 19:56 UTC

    Separate the methods from the procedures into different packages. I'll usually end up with the procedures being tiny wrappers around methods.

    I've done the "detect if you are being called like a method or not" dance before. In hindsight, I just don't find it worth it in the long run. I always end up being burned by the relative ambiguity (calling things the wrong way or writing something new the wrong way or having to go with awkward new interfaces in order to prevent obviously unacceptable amounts of ambiguity).

    Having a tiny set of wrappers in a separate package makes the situation very clear and prevents boilerplate being pasted into the top of each method-as-desert-topping-and-floor-wax sub.

    - tye        

Re: module w/ object-oriented and functional interfaces: best practices?
by Anonymous Monk on Sep 21, 2012 at 17:34 UTC
Re: module w/ object-oriented and functional interfaces: best practices?
by temporal (Pilgrim) on Sep 21, 2012 at 19:10 UTC

    Thanks for the replies, guys. Apologies about the lack of code provided BrowserUk, I definitely put too much emphasis on "convert my code" when what I really want is "how do you guys implement this in your own modules?".

    Before going any further, I do understand the mechanics behind Perl OO - blessed hashref, etc. Thank you for reiterating them for me =)

    My main reason for wanting to implement this is that I have to work with these modules, which are essentially libraries of functions. In some calling scripts all you want is some subroutine out of the module. I don't want to mess with all that OO stuff - quick, lightweight, simple and easy with Exporter.

    On the other hand sometimes (increasingly so, for me) I'll want something with a little more permanence and I think that some of my modules are "growing" in this direction. I want to create several module objects which have individually loaded configs either from the constructor call or loading config files. Then working with the libraries within these modules is simpler than having to pass all my configuration options to an exported function or having to reload configurations for the module.

    If your procedural functions were written (or could be easily modified) to take a hashref containing the data they operate on, then making the module "OO" could be as simple as adding a constructor sub that builds a hash from its inputs, blesses a reference to it and returns it.

    I only pass a hashref in this manner for functions that require more than 3 arguments. So I'm partially in that boat. I think this is what I meant by converting my functions to create a temporary object each time a function is called.

    Having both is often no more than paying lip service to peoples wont to have things work in one particular way.

    Am I wrong to want to do this? I like making everyone who's using my module happy. I like the flexibility.

    Anyway, I am getting a very "look at your module usage and make up your mind one way or the other" vibe from you guys on this one. I'm a little surprised since so many CPAN modules support both pragmas.

    I thought there might be a best practice style of going about this. Could be that the best practice style is decide which pragma is more important, write the module to support that, and then cobble the other pragma in as needed.

    Strange things are afoot at the Circle-K.

      Am I wrong to want to do this? I like making everyone who's using my module happy. I like the flexibility.

      My point is that if a module or library really benefits from being constructed using the OO style; a procedural interface to it doesn't make sense.

      Conversely, if a module/library lends itself better to a procedural interface, adding an pseud-OO interface also, doesn't add flexibility, it adds unnecessary complexity.

      For example. You'll find a bunch of statistics modules on CPAN that have OO interfaces. You create an instance; add your data to that instance either item by item or on mass; and then call various methods on it to retrieve the stats.

      Which is fine if your script consists of: reading a file of data; and then outputting the required stats to the screen or a file. Only.

      But most of the time in my scripts; the data I want stats on is wrapped up in some larger data structure that gets used for drawing graphs or processing to images or any number of other processes. The stats is just a small step in the overall picture.

      Having to copy the data from where it lives into a object in order be able to get the average and stddev, means duplicating the memory and copying stuff from place to place; when all I want or need is:

      ... my( avg, $stddev ) = avgNstddev( $bigStruct->{something}{something +else} ); ## hash expression yields an array ref. ...

      In this case, an OO interface would require:

      ... my $stats = Stats:Mod->new; $stats->add( $_ ) for @{ $bigStruct->{$something}{$somethingelse} +}; my $avg = $stats->avg; my $stddev = $stats->stddev; $stats->empty; ...

      Not only more complex code; but in the process I trebled the memory requirement: the original source of the data; the list created by the for; and the copy held internally by the object. If the array is large, that's a problem.

      If the ...s in the two snippets above are:

      for my $something ( keys %bigstruct ) { for my $somethingelse ( keys %{ $bigstruct->{ $something } } ) { ... } }

      It starts to be a big and expensive problem.

      Now, for some types of stats, an OO interface makes sense because some calculations can benefit greatly from caching results from earlier calculations. Simple example, If you need both the average and stddev, having separate functions for each means you'll probably end up calculating the average twice. Whereas an object interface can cache the average internally and reuse it for the stddev; or if stddev is called first, calculate and cache the average whilst calculating the stddev and when (if) the average method is called, avoid having to recalculate it by returning it from cache. And both cached values can be reused when calculating the return for other methods.

      But rather than then forcing me to add my data to an object to get the simple values; make the average() a separately callable sub (or a class method) that takes an array ref, usable procedurally. Then have the object interface use that internally to calculate averages, Ditto for stddev(). Have it be another sub/class method with parameters of the array ref and their average.

      That way, for the simple cases, I can use the procedural interface, and only use the OO interface for the more complex calculations.

      Of course, the best interface might be to have the OO stats module take an array ref as the argument to its constructor:

      my $stats = Stats::Module->new( $bigstruct->{ $something }{ $something +else } ); my( $avg = ( $stats->average, $stats->stddev ); $stats->done;

      Still a little more code than the simple procedural interface; but with the advantage that it can handle the more complex operations without needing to replicate the data internally. Of course, it comes at the cost that if you modify the data between calls to methods for the various stats, it could try calculating the stddev for the modified data by reusing the cached average calculated against the original data.


      With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

      RIP Neil Armstrong

Re: module w/ object-oriented and functional interfaces: best practices?
by temporal (Pilgrim) on Sep 21, 2012 at 22:02 UTC

    Wow, great read BrowserUk.

    Conversely, if a module/library lends itself better to a procedural interface, adding an pseud-OO interface also, doesn't add flexibility, it adds unnecessary complexity.

    This is the situation that I am in. And running into that complexity while trying to kludge in the pseud-OO interface led me to creating this post! =)

    You make some great points which hit surprisingly close to home for me. Particularly - avoiding those expensive recalculations over larger data sets when you want to perform multiple operations. Thank you, I learned a lot and now have some food for thought for implementing some tools I have in the works.

    After having been warned, warded, and wizened by all the previous posts, tye, your solution sounds like the cleanest and easiest not to mention most maintainable way to implement what I want to do. Which is funny, because the first reply in my OP's related link (Module provides both of functional interface and object-oriented one) argues against doing (sorta-kinda) what you describe!

    Anyway monks, signing off for the weekend and thanks again for all of you donating a little enlightenment.

    Strange things are afoot at the Circle-K.

Re: module w/ object-oriented and functional interfaces: best practices?
by sundialsvc4 (Abbot) on Sep 21, 2012 at 18:29 UTC

    Echoing BrowserUK’s comments, and after up-voting his post, I would emphasize that a running, stable application is a very important thing not to be changed lightly.   In my experience it is best to add objects to existing applications, when and where and if that makes sense, and to generally let a sleeping dog lie.

    Perl’s implementation of objects is just as pragmatic as the rest of the language.   A constructor routine, with a by-convention choice of name, that returns a blessed (hash...) reference; a fast-and-efficient notion of “blessed,” and the blessed-reference is always the implied first parameter to the function.   This gets the job done simply and efficiently without compelling you to seriously change either your thinking or your code.   (Typical battle-tested Perl wisdom ...)

    Yes, the object paradigm (in Perl) is quite useful, because it gives you “a convenient briefcase in which to store your coat,” or anything and everything else that you need to carry around with you, and it makes sure that you won’t misplace it.   Plus, a naturally-simplified calling convention for the clients to use.   Yes, lots of formerly-function based packages did “convert” to an object API, but then again, I daresay that there are other packages that went the other way.   They didn’t do it because someone just felt embarrassed about how the package worked before.

    “Best Practice” is, and forever will be, a general guideline ... a frequently-beneficial categorical suggestion, but made by someone who does not know and cannot know your application’s unique situation.   But I would not tamper with what works, just because of it.   Rather, I would permit my application to evolve naturally, trending in this new direction as it makes logical business-sense to do so.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://994939]
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (4)
As of 2024-03-29 04:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found