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

Procedural vs OOP modules

by Bod (Curate)
on Oct 28, 2021 at 22:52 UTC ( #11138156=perlquestion: print w/replies, xml ) Need Help??

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

When writing a new module, how do you decide whether to write it as an OOP module or a procedural module?
In the modern era of Perl, is there any reason to create a new procedural module or should they all be OOP?

Replies are listed 'Best First'.
Re: Procedural vs OOP modules
by choroba (Archbishop) on Oct 28, 2021 at 23:08 UTC
    You probably already know you need to implement several subroutines. Is there any state that needs to be shared among them? If so, use OOP; if not, there's no need for it.

    It's not that hard to start one way and switch to the other paradigm later if needed, e.g. when you discover you need subclassing or some kind of a testing trick (mocking, dependency injection) for a procedural module or you realise you haven't used $self in any of the methods.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      > Is there any state that needs to be shared among them? If so, use OOP

      Actually you can share state in the package variables, but only one set of them.

      This pattern is similar to a singleton object in OOP.

      (The modular interface of Data::Dumper is a good example for that, with the global config vars, like $Data::Dumper::Indent )

      But you are right, a very good reason to use methods instead of functions is if they all start to look like this

      package Do_Something my %repeated = init(); foo( X, %repeated); bar( Y, %repeated); ...

      then it's obviously better to write

      my $obj = Do_Something->new(%init); $obj->foo(X); $obj->bar(Y);

      especially if there is a chance that you might need to have different initializations in the same code

      my $obj2 = Do_Something->new(%other_init);

      ( see Data::Dumper again with it's alternative OOP interface, where the configs are encapsulated in the objects like $OBJ->Indent([NEWVAL]) or $OBJ2->Indent([NEWVAL]) )

      On a side note: I prefer to think about objects like actors which do things, not containers which have attributes.

      So reducing them to shared data is not the whole picture.

      Last but not least: PBP lists some criteria when to use what...

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        For completeness: You can also share state between functions with the help of closures.
        - Ron

      So if I only want methods that take a value and return another value, it will be procedural...

      That's a beautifully elegant exppanation choroba - thnk you

        I don't think this is necessarily true. The whole "data encapsulation" thing is a little fuzzy in practice with Perl. I think most people can agree to this. Having that deref syntax is nice, particularly if you tend to use a lot of hash refs.
Re: Procedural vs OOP modules
by stevieb (Canon) on Oct 29, 2021 at 00:23 UTC

    I'm totally on board with what choroba said.

    If state needs to be shared between methods or even modules/classes, and/or you want to store an object of a different class in an object so you can make calls on it through the main object (eg. $computer_obj->video_card_obj->display();, OOP works (my RPi::WiringPi is a good example of that example, almost literally).

    If no state needs kept, no magic between objects needs to happen, and your subroutine naming conventions won't likely clash with already-imported functions, procedural is the way to go (eg. Bit::Manip).

    Other times, I've provided both a functional and object oriented interface within the same module for the same subroutines (eg: WiringPi::API).

    Personally, the vast majority of the code I write keeps state, so I'd go as far as to guess that 90%+ of the 55 CPAN distributions I've published are OOP.

Re: Procedural vs OOP modules
by eyepopslikeamosquito (Bishop) on Oct 29, 2021 at 02:29 UTC

    > When writing a new module, how do you decide whether to write it as an OOP module or a procedural module?

    Bod, great to see you thinking about this important topic so early in your programming career!

    The most important aspect of any module is not how it implements the facilities it provides, but the way in which it provides those facilities in the first place.

    -- Practice No. 1 in Damian Conway's Ten Essential Development Practices

    Given its crucial importance, it's well worth spending considerable time studying the dark art of interface design.

    My most comprehensive reference on this topic is On Interfaces and APIs. Other useful references:

    For your question, an outrageously simple answer is to ask: Do I need more than one of these? If the answer is yes, OO is indicated, otherwise procedural.

      great to see you thinking about this important topic so early in your programming career!

      Haha! Thanks eyepopslikeamosquito

      I suppose the second part of the original question was the more significant part for me...is there ever a case in the current day to write a module that isn't OOP. The discussion has certainly answered that there is.

      To keep writing style consistent across our company, we use a style guide which, among other things, sets a minimum Flesch Kincaid Grade Score. We mostly use Hemingway to check our copy. However, we have internal editors for blogs, emails, etc. so I thought it would be helpful to have a real time Grade Score on these. To that end, I found an existing Javascript Flesch Kincaid module. I wanted Javascript to avoid lots of AJAX calls.

      To my surprise, I found that the Grade Score from this was vastly different from Hemingway for the same piece of text. I was surprised because Flesch Kincaid is a well-defined mathematical formula. After some investigation, it seems the difference comes from the difficulties in programatically counting syllables.

      So I thought I'd write my own module to obtain a Grade Score. I know there are Text::Info and Lingua::EN::Fathom. The latter I am using to create a copy checker that also includes a ratio of first person to second person pronouns. Both these modules count syllables using Lingua::EN::Syllable which the author notes is not entirely accurate although this is not especially important for my application.

      Far more important to me is consistency. I need a Perl module and a Javascript module that function the same way and obtain the same result for a given block of text...any block of text over a dozen or so words. Hence looking at writing my own. That way I can create the syllable counting regexp in Javascript and Perl in a way that should produce consistent results in both languages.

      My instincts tell me that this is best not done using an OOP module. But I couldn't explain why. Now, given the helpful and informative discussing, I understand some of why my instincts were right. I guess my judgement was also clouded by having only written OOP modules recently. I also think that the choice was mostly right for those too.

        > Is there ever a case in the current day to write a module that isn't OOP. The discussion has certainly answered that there is.

        Yes! Not everything has to be OO. That's why I enjoy Perl and C++; their designers appreciated that OO is not necessarily the best solution to all problems.

        As an aside, I was once hired as a Java programmer but its emphasis on OO felt oppressive and I quit soon after (as mentioned here). Though I managed to hold down a C job without resigning, I didn't enjoy its primitive abstractions (for example, compare this rosetta C function with the other languages!) nor its flawed standard library, such as strtok, singled out for a dishonourable mention at On Interfaces and APIs. I suppose that's the main reason I gravitated towards C++ and (later) Perl. (BTW, Stroustrup describes here why he designed C++ as a multiparadigm programming language).

        > I know there are Text::Info and Lingua::EN::Fathom. The latter I am using to create a copy checker that also includes a ratio of first person to second person pronouns. Both these modules count syllables using Lingua::EN::Syllable.

        Rather than starting from scratch, you might like to model your new module on your favourite from these three (they all seem to have decent authors):

        • Lingua::EN::Syllable by Neil Bowers. I see Neil (aka neilb) is from Marlow UK, a short scenic drive from your (more working class?) Coventry UK. Neil is a prolific CPAN author ... and a PAUSE Admin, PSC member, and White Camel award recipient!
        • Lingua::EN::Fathom by Kim Ryan. I've actually met Kim - he's an excellent programmer, a gentleman (and a gentle man) ... and the founder of Sydney.pm.
        • Text::Info by Tore Aursand. I'd never heard of Tore before, but I see he's published nine CPAN distributions. Though the name suggests Scandinavia, I trust you're willing to forgive and forget the Norman Conquests. ;-)

Re: Procedural vs OOP modules
by hippo (Bishop) on Oct 29, 2021 at 09:32 UTC

    At a high level the question is, "Should whatever this module represents be an object?" and the answer is often obvious from the context of its use. If the code to use the module will (or could) have more than one of the thing, then it should be an object and your module should represent a class.

    At a low level the question is, "Does this code need to work with existing code which is already OO?". eg. if you are looking to extend/replace existing OO code then equally the answer should be yes.

    You can always mix procedural and OO within one module if required/desired. And don't forget about functional either. Horses for courses.

    Finally, if you still aren't sure which way to go, try writing your tests first (before the code which they will test). That may make the preferred paradigm clearer.


    🦛

Re: Procedural vs OOP modules
by Tux (Canon) on Oct 29, 2021 at 14:06 UTC

    Another criterion might be the environment you are using this new module in: if it is DBI, it is very likely your modules would be easier to write and use using OO. If it is a subclass on something that is purely procedural, OO might be overkill and less intuitive.

    You can have modules that be both. Transparantly, and have the end users choose what API they prefer.

    So my advice would be to write your module in the way you are most comfortable with and then add the alternate interface later.

    (of course if all the good advice you already recieved did not already convince you procedural or OO is better for what you are writing).

    I once wrote Spreadsheet::Read purely functional, and added the OO interface later as a proof of concept. That new OO interface was so much easier to work with (for me) that I switched almost completely for my own scripts, but the old(er) scripts still work.


    Enjoy, Have FUN! H.Merijn
Re: Procedural vs OOP modules
by bliako (Monsignor) on Oct 29, 2021 at 08:25 UTC

    also note that you can have functions in an OOP module which do not need to know the state, do not need to take the object as input. In which case you just call them as you would without being packaged in an OOP module. That's equivalent of static functions in C++ and Java.

    my $xx = XX->new(); $xx->oopfunc("abc"); XX::staticfunc("abc"); {# the OOP module XX package XX; sub new { my $class = shift; my $params = $_[1]; my $parent = ( caller(1) )[3] || "N/A"; my $whoami = ( caller(0) )[3]; my $self = { 'state' => 'xxx' }; bless $self, $class; return $self } # call it as # my $xx = XX->new(); # $xx->oopfunc(...); sub oopfunc { my $self = shift; my $params = shift; print "i am using state: ".$self->{state}."\n"; } # call it as XX::staticfunc(...); sub staticfunc { my $params = shift; print "static func called.\n"; } 1 }
      There are "class methods" like Class->new() which expect $class as first arg, but you propagate Class::func() which doesn't.

      I'm not thrilled about mixing $obj->methods() which expect $self and such pure functions Class::funcs() ...

      This has some code smell for me and should be well thought of.

      I think this might sabotage many patterns like sub-classing...

      Can you give an example where this makes sense?

      update

      I took a quick glimpse into static functions for C++ and Java and they look very much like class methods to me.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        Can you give an example where this makes sense?

        When one wants to use such functions without instantiating an object first. But I guess one can use class methods (as per your definition) and *make sure* to consume the 1st param which will be the classname.

        I think this might sabotage many patterns like sub-classing...

        You mean because staticfunc will not be seen in a sub-class unless you do both: use base 'Class'; and use Class; ? I guess yes that's a problem.

        I took a quick glimpse into static functions for C++ and Java and they look very much like class methods to me.

        Yes, except that they don't mess with the parameters to provide a classname.

        Do you suggest that there is no use-case for staticfunc(), but instead convert it into a class method and consume the 1st param, in order to provide accessing it without instantiation first? OK, fine. But I think it is important to provide access to such methods which do not need the state and can be called without instantiating a dummy object first which will be of no use whatsoever. So I think what I posted is useful, but I can edit it to use class methods when this dialogue ends.

        bw, bliako

Re: Procedural vs OOP modules
by perlfan (Vicar) on Oct 29, 2021 at 08:57 UTC
    I've come to the conclusion that what most people want when they think of OOP is Perl are is this "oop" interface that uses uses the $myobj = FOO->new; idiom + accessors to fields. E.g., the more I use Util::H2O, the more this is also clear to me - at least in what I want.

    My recommendation is to go with the "oop" interface but only for the instantiation of the "object" and for providing accessors for the fields. I'll also add, that when I see use a module that doesn't provide this, it feels a little yucky. Beyond that, Perl "OOP" fur real is the domain of an extremely vocal but tiny minority. It is very true that going beyond the means for instantiating the "object" and providing accessors necessarily yields code that looks - and behaves - like a completely different language. Again, going back to my experience with Util::H2O I've been able to summarize my desire for "improving" OOP support in Perl to go no further than a better bless that provides the kinds of things h2o provides, but can still be used in the same way - h2o comes close to achieving this but to get the last bits of what I want requires non-idiomatic tricks.

    Namely, a) using h2o to define a "class" and b) taking an already blessed reference and adding accessors to it as easily as it does for non-blessed hash references. What I really want is to be able to have a bless that can magically add accessors like h2o, but that can also be used to do surgery on already blessed references - and also remain the basis for which "OOP" modules get OOP'd.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (5)
As of 2022-05-24 09:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you prefer to work remotely?



    Results (82 votes). Check out past polls.

    Notices?