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

Hi Monks,

I'm from a C++ background, and I'm struggling with some of Perl's library and OO-like features. I understand that Perl doesn't allow full encapsulation of data with in a package. What I don't quite understand is *how* I should go about passing data that I might want to be "private" to a package.

To be more precise, I have a package that will have three basic subroutines:

sub search_simple($) { # do stuff } sub search_normal($) { # do stuff } sub search_extended($) { # do stuff }

The thing is, each of these subs do very similar things, but with different parameters which are set in the subs. I would like to have something like this:

sub process_search($) { # do stuff } sub search_simple($) { # set parameters return process_search($); } sub search_normal($) { # set parameters return process_search($); } sub search_extended($) { # set parameters return process_search($); }

So, the crux of my question is: how do I set the parameters most effectively? Will our create a package-level variable? Or should I just be passing around hash-references so that they're destroyed after the sub is called (assuming the reference-count is 0)? Or am I maybe thinking about this all wrong?

Any suggestions/advice/criticism encouraged! :)

Replies are listed 'Best First'.
Re: Package-Level Variables
by sauoq (Abbot) on Jul 25, 2003 at 19:33 UTC
    Will our create a package-level variable?

    Well, it will create a global variable in the package's namespace. If you want the data to be private to your package, you shouldn't use our; you should put your package in a file by itself (or in a block) and use a lexical variable declared with my instead.

    Or should I just be passing around hash-references

    Probably, yes. (A single hash ref would probably suffice, right?) For instance, I'd probably have subs something like this:

    sub process_search { my $options = shift; # . . . } sub simple_search { my $options = { SearchType => simple, # Contrived parameters for MaxMatches => 10, # illustrative purposes. }; process_search( $options ); }

    Any suggestions/advice/criticism encouraged!

    I notice you are using prototypes. Don't. At least not until you understand that they aren't anything like what you probably expect coming from a C++ background. Read Tom Christiansen's article about them for a detailed explanation of why they aren't for general use.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Package-Level Variables
by halley (Prior) on Jul 25, 2003 at 20:29 UTC

    This is meant for rough "Rosetta" comparison, and clearly the two languages have some very different idioms and distinct semantics on various features.

    Foo.pm:
    package Foo; our @ISA = qw(Exporter Phoo); our $Bar = 6; my $Whig = 5; sub new { my $count if 0; $count++; my $self = { }; $self->{baz} = 4; return bless($self, shift()); } sub foo { my $self = shift; my $arg = shift; local $Whig = 3; $self->my_Phoo_func($Whig, $arg, $/); }
    Foo.cpp:
    #include "Foo.h" // class Foo : public Phoo { ... }; static int Foo::Bar = 6; static int Whig = 5; Foo::Foo() { static int count = 0; count++; this->baz = 4; } void Foo::foo(int arg) { int save_Whig = Whig; Whig = 3; this->my_Phoo_func(Whig, arg, "\n"); Whig = save_Whig; }

    I mention the C++ this twice for clarity, though these are implicit, not required, and rarely mentioned in C++. Note that static means something entirely different inside a C++ function versus outside a function. The Perl hack my $foo if 0; is not something I recommend, but does emulate C++'s static lexical variables (or as Larry calls them, "persistent" between calls).

    --
    [ e d @ h a l l e y . c c ]

      my $count if 0;

      Ugh... do you actually use that? I thought that was only an unintended side-effect of how scratchpads are implemented. Am I confused on that point?

      I would use a named closure for emulating static vars

      { my $static; sub count { $static++ } }

      Also, I think "use Foo;" is more analogous to "#include <Foo.h>" than "package Foo;" is.

      -sauoq
      "My two cents aren't worth a dime.";
      
        One, as mentioned, I don't recommend the hack for persistent variables. I think it's interesting. I haven't seen your method in use, but I've seen the hack.

        Two, you wouldn't use Foo; inside Foo.pm. This Rosetta line is the statement which establishes the namespace for the subsequent implementation code. (Hence the comment following which shows what the header file implies.) In C++, both the user and the implementor of a class typically include the header file; in Perl, only the user would 'use' the file as if declaring the intent to call upon its interface.

        --
        [ e d @ h a l l e y . c c ]

      I didn't quite follow the discussion about whether package or use is more analogous to #include. I had thought there wasn't really a clear analogue to package in C. But aside from that, this was extraordinarily helpful. Thank you very much!
Re: Package-Level Variables
by rir (Vicar) on Jul 25, 2003 at 20:52 UTC
    ... *how* I should go about passing data that I might want to be "private" to a package.

    Create your variable privately then don't pass it outside of your package. Likely you want to use my to create the variables.

    I am confident that you do not want to be using function prototypes on any of these routines. That is you want: sub function {# do stuff instead of  sub function($) { #do stuff

    Function prototypes are a completely different beast in Perl from those in C or C++. You very rarely want them. To emulate the control that C-style prototypes give you you explicitly check @_.

    # off-hand code sub some { croak "Bad arg count to some" unless @_ == 2; croak "First arg to some not a ref" unless ref $_[0]; # etc. etc. return do_stuff(); }
    Good perling.
Re: Package-Level Variables
by skyknight (Hermit) on Jul 25, 2003 at 19:32 UTC

    If you want to create a package global variable that is private, you should use 'my', not 'our', as 'our' will expose the variable to external tampering. Really, though, I'm not particularly fond of your idiom of setting global parameters that effect the behavior of other subroutines. This creates a level of indirection that will make debugging more difficult. I would instead favor an idiom of having your process_search subroutine take multiple arguments, having each of your specific search functions pass along the search that was handed to them, as well as pass along some parameters the control the behavior of your general process_search function. This makes your program behavior more functional, eliminating confusing and unnecesary state information from the class.

Re: Package-Level Variables
by bart (Canon) on Jul 26, 2003 at 11:23 UTC
    In your place, I wouldn't make it three subs. I'd make it just one sub, with a mode parameter indicating how you want it searched. Like this:
    search(simple => $stuff); search(normal => $stuff); search(extended => $stuff);
    The definition of the search routine then could look like this:
    use Carp; sub search($$) { my(%parameter); # Those parameters that you set my $mode = shift; if($mode eq 'simple') { # set parameters to simple ... } elsif($mode eq 'normal') { # set parameters to normal ... } elsif($mode eq 'extended') { # set parameters to extended ... } else { croak("mode '$mode' not supported"); } # Now just do your process_search ... }
    And your problem simply evaporates.

    Any former syntax related problems, i.e. use of an invalid mode, would now only be detected at runtime, thus when the call is made. But actually that's not different from the original code, where a call to an undefined sub would only be detected when an attempt to call it, is made.

Re: Package-Level Variables
by CountZero (Bishop) on Jul 26, 2003 at 07:18 UTC

    This is what the Camel-book (3rd ed.) says about the our declaration on p. 133:

    Lexically scoped Global Declarations: our

    A beter way to access globals, especially for programs and modules running under the use strict declaration, is the our declaration. This declaration is lexically scoped in that it applies only through the end of the current scope. But unlike the lexically scoped my or the dynamically scoped local, our does not isolate anything to the current lexical or dynamic scope.

    our therefore does quite the opposite of what you think: it creates a global variable, accessible to all from anywhere in your program.

    The way to do what you want is to declare the variables in your subs with my and to pass the necessary parameters to your sub process_search() through its argument-list.

    All very clean and well-encapsulated.

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: Package-Level Variables
by demerphq (Chancellor) on Jul 27, 2003 at 11:45 UTC

    You may have noticed that in all the responses so far nobody used function protoypes. They dont do what you probably think they do and you should avoid them like the plague. They are one of Perls many "dont use this until you know why this should be used, if ever" features. Specifically in your case

    sub search($) { ... }

    is both misleading and IMO wrong. Methods _always_ take at least one parameter ($self) and prototypes are ignored when a subroutine is invoked via a method call. So they wouldnt directly break any OO code involved, but it is common knowledge that

    $obj->method($parameter);

    is more or less equivelent to

    PACKAGE::method($obj,$parameter)

    But the prototype you have used would prevent the second call from being made. This will only annoy the power user who deliberately did this for some presumably good reason, and really does no good so you should just drop the prototype.

    sub search{ ... }

    Or am I maybe thinking about this all wrong?

    Quite possibly yes. Concern over private data managment is much more important in languages where you are linking to precompiled libraries that you do not have the code for. In perl its usually quite sufficient to mark data as private by some form of notational convention, and then leave it at that. The issue in Perl for private data is mostly to make accidental corruption unlikely not to make the data 100% absolutely unaccessable by external entitities. (Which is in fact more or less impossible, given the various devious modules out there.)


    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...