http://qs321.pair.com?node_id=586646

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

Hello wise Monks!

I am studying Perl OO programming and I have learned how to create classes using modules as packages saved in .pm files. I was wondering whether it is possible to do all that in a single file, so that distributing a perl script would be easier.

I have started experimenting with defining multiple packages in a single script and I seem to be able to create classes and new objects, but so far I had no success trying to implement inheritance.

With the following code I am trying to define two classes: Class1, which is the parent, and Class2 which should inherit from Class1. I was hoping it would work, but actually it doesn't and my Class2 is not a child of Class1 (it cannot find the method PrintHello).

Is this just impossible within a single file or am I missing something?

Here is the code:
#!/usr/bin/perl use warnings; use strict; use Data::Dumper; my $max = Class2->new(NAME => 'MaxKlokan') ; $max->PrintHello(); print Dumper $max; exit; ####################### { package Class1; sub new { my $classname = shift; my $self = {@_}; $self->{NAME} = undef unless $self->{NAME}; $self->{AGE} = undef unless $self->{AGE}; bless($self,$classname); return $self; } sub PrintHello { my $this = shift; print "Hello $this->{NAME}\n";; } } ####################### { package Class2; require Class1; @Class2::ISA = qw(Class1); sub new { my $classname = shift; my $self = Class1->new(); $self->{TITLE} = undef unless $self->{TITLE}; bless($self,$classname); return $self; } }
  • Comment on Defining classes and inheritance using packages within a single .pl file, without creating modules
  • Download Code

Replies are listed 'Best First'.
Re: Defining classes and inheritance using packages within a single .pl file, without creating modules
by jbert (Priest) on Nov 29, 2006 at 10:18 UTC
    Interesting.

    The root cause is that perl has read your entire script from start to finish and compiled it, so that Class2::new exists as a method for you to call.

    What hasn't happened at the time you are trying to execute $max->printHello is that the line @Class2::ISA = qw(Class1) hasn't been executed yet...simply because it is further down the script.

    Normally, you pull in an external module as a seperate file with a use directive. This is like a require with a BEGIN block around it, which runs all the code in the module as well as compiling it.

    You could fix this issue by either moving the { package ... } sections before your code or putting a BEGIN { ... } block around your package definitions.

    However, I'd personally do neither of those. The best way these days to declare inheritance is use base, which also has the handly property of being a compile-time construct.

    Also - to address your other issues, perl OO gets quite a bit nicer if you start to use some of the CPAN modules. The problem is working out which ones to use :-).

    Class::Accessor is quite good, and Moose looks like it might be the way of the future. There is a discussion about this here from April of this year.

      In general, I agree with your excellent advice, jbert, except for this minor point:
      However, I'd personally do neither of those. The best way these days to declare inheritance is use base, which also has the handly property of being a compile-time construct.
      IMO base is a crap module, it tries to handle both loading external files and inline packages, but it tries Too Much Magic™ and in some cases it may fail. It's failed many times for me in the past. I don't trust it any more.

      As a result, you may have to reorder the definition for your packages and possible even add a $VERSION variable (in an inline package!). All that to please base. Yuck.

      I wish we could bypass the magic, and explicitely tell it to load a module, or just skip that phase.

      If it wasn't for that annoyance, base would indeed work very well, for the reasons you give.

        That's interesting - and serves as an additional data point that consensus on current perl OO "best practice" is a bit limited at the moment. Yes, TIMTOWDI, but that can be confusing for newcomers.

        Do you think that these tutorials are still current? (Actually, I should have linked to these at first, sorry about that).

        By the way, would you care to elaborate on base's failure modes? Since you've caused me to look more closely, I can see how it might hurt if you're defining a module inline with the same name as one available as a seperate file via require - is that the main issue?

        IMO base is a crap module, it tries to handle both loading external files and inline packages, but it tries Too Much Magic™ and in some cases it may fail. It's failed many times for me in the past. I don't trust it any more.

        Use it all the time. Never had a problem with it :-)

        I don't really think it's that magical. Two rules:

        • If the package is already loaded - use it
        • Otherwise, try and use it.
      A reply falls below the community's threshold of quality. You may see it by logging in.
      Thank you for the good advice. For this particular case, IMHO the neatest way is to turn the { package ... } blocks into BEGIN { package ... } blocks, and remove the require Class1; line. If I don't remove that line, the block for Class1 is evaluated twice and I get a warning about new() and PrintHello() being redifined. The same warnings are issued if I use base qw(Class1); (without BEGIN). I guess that's because of the same reason (base implies a require). It seems therefore that use base is not the way to go for inline class definition.
        Well, require is for loading files, which you won't need at all if everything is inline, so removing it is a good idea.

        The reason I would prefer 'use base' (bart's objections notwithstanding) over assigning directly to @ISA is that I don't like having run-time code in modules. (It also seems to work fine in a test here - just remove the @Class2::ISA line and use base qw/Class2/; instead of require Class2;).

        I would say that having a script with a single entry point and execution following function/method invocation, it is easier to see what is going on. Otherwise one must read through the whole file to check for side-effects.

        For example, I commonly have a

        main</c< function: <code> #!/usr/bin/perl use warnings; use strict; exit !main(@ARGV); sub main { ... } sub foo { }
        The benefit of the early exit is that a maintainer can see that there isn't any run-time code lurking down between any later subs (since execution never flows down there). It also means that any lexical variables I use in main aren't accidentally accessible in the functions within the script. (OK, it might make sense to have some file-scope vars, but such things should stand out, not be the default because I'm writing code at the toplevel).

        Yes, for quick scripts etc it is handy to have the scripting behaviour, but by the time we bring OO or helper functions in, we probably have enough complexity that this kind of discipline is beneficial.

        So...when using perl as a programming language (as opposed to a 'scripting' language), I think it is harmful to have code at the top-level - it's effects are too wide-spread (variable scope too large) and the code can be spread over too large an area to see easily.

Re: Defining classes and inheritance using packages within a single .pl file, without creating modules
by fenLisesi (Priest) on Nov 29, 2006 at 09:25 UTC
    PAR may help you with distribution. Cheers.

    Update: There are multiple errors in the code. One of them is that the Class2 constructor calls the Class1 constructor without arguments. You might also want to consider whether an age of zero or the empty string as a title are acceptable. There are many CPAN modules that will help you lay out your OO structure. HTH.

Re: Defining classes and inheritance using packages within a single .pl file, without creating modules
by chromatic (Archbishop) on Nov 29, 2006 at 18:46 UTC
    I was wondering whether it is possible to do all that in a single file, so that distributing a perl script would be easier.

    That sounds like false laziness, especially given that you have to avoid a lot of normal Perl idioms to make this possible. PAR (suggested elsewhere) is one good solution. Another solution is to study perlmod and Module::Build and even Module::Starter or ExtUtils::ModuleMaker. (Some people like ExtUtils::MakeMaker, but I find it hideous to customize and debug.)

Re: Defining classes and inheritance using packages within a single .pl file, without creating modules
by perrin (Chancellor) on Nov 29, 2006 at 18:48 UTC
    Although Perl lets you do this, it's usually a bad idea. It will lead to confusion when people are trying to debug your code and can't figure out where the mysterious packages are coming from. Much simpler to just use multiple files.
      This modified code demonstrates package inlining and inheritance. For small scripts, I like to inline a package once in a while, escpecially when subclassing a standard module. if I put the main package before the other packages, it doesn't work.
      #!/usr/bin/perl use warnings; no warnings qw/uninitialized/; use strict; use Data::Dumper; ####################### package Class1; sub new { my $classname = shift; my $self = {@_}; $self->{NAME} = undef unless $self->{NAME}; $self->{AGE} = undef unless $self->{AGE}; bless($self,$classname); return $self; } sub PrintHello { my $this = shift; print "$this->{TITLE}\tHello $this->{NAME}\t$this->{AGE}\n";; } ####################### package Class2; our @ISA = qw(Class1); sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{TITLE} = 'This is Class2'; return $self; } ######################## package main; my $max = Class1->new(NAME=>'Someone Else',AGE=>42); my $max2 = Class2->new(NAME => 'MaxKlokan',AGE=>21) ; $max2->PrintHello(); print Dumper $max; print Dumper $max2; exit;
        If you put the others in a BEGIN block, you can put main first.