I'm sure my problem is related to Namespaces and scoping, but I need some help!
I'm writing an OO Perl script with a fairly large number of classes and a requirement to minimise external module use (don't ask... I know, I know...)
So, I want to use UNIVERSAL to offer a logging method that every object can use.
Here's a very simple example that I've just whipped up.
use strict;
use warnings;
package House;
sub new {
my ( $class, %args ) = #_;
my $self = {
colour => $args{colour},
size => $args{size},
};
bless $self, $class;
return $self;
}
package Boat;
sub new {
my ( $class, %args ) = #_;
my $self = {
doors => $args{doors},
roof => $args{roof},
};
bless $self, $class;
return $self;
}
package main;
my $obj = Boat->new( colour => "red", size => "big" );
$obj->_logger("created a big red boat");
my $obj2 = House->new( doors => 1, roof => "yes" );
$obj2->_logger("created a house with a door and roof");
package UNIVERSAL;
use POSIX qw( strftime );
use Sys::Hostname;
my $error_log
= hostname() . "-" . strftime( "%Y-%m-%d_%H.%M", localtime ) . ".log";
sub _dump {
my ( $self, $data, $filepath ) = #_;
open my $fh, ">", $filepath or die "Cannot write to $filepath: $!";
print $fh $data;
}
sub _logger {
my ( $self, $data ) = #_;
my $timestamp = strftime( "%Y-%m-%d %H:%M:%S", localtime );
$self->_dump( $timestamp . " " . $data, $error_log );
}
__END__
The problem is the $error_log variable in the UNIVERSAL namespace doesn't seem to be accessible by the objects in other classes in the same way that the UNIVERSAL methods are.
Errors with my $error_log:
Use of uninitialized value $filepath in open at ./test_uni.pl line 47.
Use of uninitialized value $filepath in concatenation (.) or string at ./test_uni.pl line 47.
Cannot write to : No such file or directory at ./test_uni.pl line 47.
Actually, now I type this I wonder if a closure with a class method in UNIVERSAL would work.
While I go and try that, does anyone have any suggestions for me please?
Thanks!
==================== UPDATE ======================
A closure with a class method in UNIVERSAL seemed to work:
package UNIVERSAL;
use POSIX qw( strftime );
use Sys::Hostname;
{
sub ERROR_LOG {
return hostname() . "-" . strftime( "%Y-%m-%d_%H.%M", localtime ) . ".log";
}
}
And then I call it in UNIVERSAL::_logger with UNIVERSAL->ERROR_LOG.
BUT! I only want the ERROR_LOG filepath to be created once at runtime. With this it will evaluate it every time...
Is this the only way? How can I access variables in the UNIVERSAL package from elsewhere?
Thanks!
The problem in your case is just that you run the code before the stuff in UNIVERSAL is set up.
Move the main package all the way down, or wrap UNIVERSAL in a BEGIN block, and it works.
Nothing weird about UNIVERSAL or my going on here.
Update: Okay, it is a bit weird that you can call _logger (so that part is loaded already), but the $error_log is not there yet. Here is a minimal example that demonstrates this behaviour (remove the BEGIN block to see the problem):
use strict;
use warnings;
ABC->hey();
package ABC;
BEGIN{
my $x = 1;
sub hey(){
print "x = $x";
}
}
Maybe this explains it:
A my has both a compile-time and a run-time effect. At compile time, the compiler takes notice of it. The principal usefulness of this is to quiet use strict 'vars' , but it is also essential for generation of closures as detailed in perlref. Actual initialization is delayed until run time, though, so it gets executed at the appropriate time, such as each time through a loop, for example.
My reading would be that the variable declaration and the subroutines are compiled before the code is executed, but that the assignment of the value does not take place until the line which does it is reached again (which in your case is after you call the subroutines which are closures around the still uninitialized value).
Related
I have really no idea, why this is wrong:
#!/usr/bin/perl
use v5.20;
package MyDate;
sub new{ bless {}, shift; }
sub AUTOLOAD{
my $f = our $AUTOLOAD;
my #h{qw[Wday Month Year]} = (localtime)[3,4,5];
}
Err:compilation error near "#h{"
If I delete my (or even if package-scoped with our):
#h{qw[Wday Month Year]} = (localtime)[3,4,5];
It will magically works. Why cannot be hash slices lexically scoped?
Edit: Yes - I have not noticed, that (localtime)[3] = mday not wday. But that is not the point. I am asking about the scope, not localtime func.
Edit2: The hash %h (the point of my question), is intended to be used inside the autoload sub (well, of course when I am trying to use it as hash slice there). Just for clarity.
#h{...} is not a variable, so you can't declare it as such.
#h{...} = ...; sets elements of %h. So it's %h you need to create.
This is done as follows:
my %h;
By the way, I doubt you have a legitimate reason for using AUTOLOAD. Keep in mind that code at the top level (at the file level) of a module will be executed when the module is first loaded in an interpreter.
I hope that you will see your mistakes from following piece of code
use strict;
use warnings;
use diagnostics;
use v5.20;
package MyDate;
sub new{ bless {}, shift; }
sub AUTOLOAD{
my $f = our $AUTOLOAD;
#my %h; # !!! without hash declaration compilation error
# 0 1 2 3 4 5 6 7 8
#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
# localtime(time);
#h{qw[Wday Month Year]} = (localtime)[6,4,5];
}
perl hash_package.pl
Global symbol "%h" requires explicit package name (did you forget to declare "my %h"?) at hash_package.pl line 15.
Execution of hash_package.pl aborted due to compilation errors (#1)
(F) You've said "use strict" or "use strict vars", which indicates
that all variables must either be lexically scoped (using "my" or "state"),
declared beforehand using "our", or explicitly qualified to say
which package the global variable is in (using "::").
Uncaught exception from user code:
Global symbol "%h" requires explicit package name (did you forget to declare "my %h"?) at hash_package.pl line 15.
Execution of hash_package.pl aborted due to compilation errors.
This is just a big WAG at what the OP is trying to do. So it's a pretty crappy SO answer according to site conventions, but I think it might help unmuddy the waters for our suffering OP.
I'm going to start with the code the OP posted, comment a bit on it, and then move to several examples of the "right way to do it".
OP's code:
#!/usr/bin/perl
So we're running a script.
use v5.20;
With version 5.20 or better. So far, so good.
package MyDate;
Now we've selected a new namespace/package called MyDate. While this isn't illegal, it is generally considered desirable to have one package per file.
sub new{ bless {}, shift; }
We have a constructor. So MyDate is going to be a class. Maybe worth looking at Moose or Moo for help with automating some of the boring crap with class construction. But there isn't anything wrong with using good, ole' classical Perl objects.
sub AUTOLOAD{
my $f = our $AUTOLOAD;
my #h{qw[Wday Month Year]} = (localtime)[3,4,5];
}
The syntax error, the source of all pain. AUTOLOAD is called to handle any unknown function calls in the namespace. So, this in-effect, intercepts all undefined method calls. MyDate has an infinite list of methods. It's probably not what is really needed.
Let's rework things a bit:
Here's my guess at the sort of thing the OP may want in their script file:
#!/usr/bin/perl
use v5.20;
use strict; # Make life easier by catching bugs at compile time.
use warnings; # Catch things that probably indicate errors, but aren't technically illegal.
use MyDate; # Load my date class
# Make some dates
my $today = MyDate->new();
my $aprilish = MyDate->new( month => 4 );
# Do stuff with them!
print_date( $today );
print_date( $aprilish );
sub print_date {
my ($date) = #_;
say "Weekday: ", $date->wday();
say "Month: ", $date->month();
say "Year: ", $date->year();
}
Library File: MyDate.pm
package MyDate;
use v5.20; # Set the minimum perl version required. Optional, but useful.
use strict; # Always
use warnings;
sub new {
my ($class, %parts) = #_;
my %defaults; #defaults[wday month year] = localtime(3, 4, 5)
my $self = bless {
%defaults,
}, $class;
for my $part ( qw/ month wday year /) {
next unless exists $parts{$part};
$self->$part( $parts{$part} ); # Call the associated method to initialize an attribute.
delete $parts{$part};
}
die "Unknown attributes: ", join ', ', keys %parts # Fatal error on unknown args
if %parts;
return $self;
}
# The other methods are mostly identical.
sub month {
my ($self, $value) = #_;
if ( #_ == 2 ) { # If two args are passed, we are a setter.
$self->{month} = $value;
}
return $self->{month};
}
That's a classical perl OO version of something like the OP is going for, I think.
It's a lot less hassle to write with Moo.
package MyDate;
use v5.20; # Set the minimum perl version required. Optional, but useful.
use Moo; # Turns on strict and warnings;
use namespace::autoclean; # Removes any utility functions so they don't show up as methods.
has 'month' => (
is => 'ro',
builder => 1,
);
has 'wday' => (
is => 'ro',
builder => 1,
);
has 'year' => (
is => 'ro',
builder => 1,
);
sub _build_month { localtime()[4] }
sub _build_wday { localtime()[3] }
sub _build_year { localtime()[5] }
But probably the best thing to do would be to take an existing date manipulation library like DateTime and use it.
#!/usr/bin/perl
use v5.20;
use strict; # Make life easier by catching bugs at compile time.
use warnings; # Catch things that probably indicate errors, but aren't technically illegal.
use DateTime; # Load my date class
# Make some dates
my $today = DateTime->today();
my $aprilish = DateTime->today()->set_month( 4 );
# Do stuff with them!
print_date( $today );
print_date( $aprilish );
sub print_date {
my ($date) = #_;
say "Weekday: ", $date->day_of_week();
say "Month: ", $date->month();
say "Year: ", $date->year();
}
Anyhow, I hope that all this is useful to the OP, and maybe, just maybe, to someone else.
How can I catch access to member variables?
$Class1->{Class2}
If the Class2 field doesn't exist, is is possible to catch this from an internal function?
You can, but you probably shouldn't. The problem here is - if you access a variable within a class directly... then you just can. You can prevent this with a couple of workarounds - and this is where things like Moose come in.
And there's a couple of slightly hacky tricks like inside-out objects (which I think aren't common practice any more - Perl Best Practice advocated them some years back) or using anonymous hashes to hold state.
But failing that - why not use an accessor, and auto-generate one using 'AUTOLOAD'.
#!/usr/bin/env perl
package MyClass;
use strict;
use warnings;
use vars '$AUTOLOAD';
sub AUTOLOAD {
my ( $self ) = #_;
my $subname = $AUTOLOAD =~ s/.*:://r;
if ( $self -> {$subname} ) {
return $self -> {$subname};
}
warn "Sub called $subname was called\n";
return "$subname";
}
sub new {
my ( $class ) = #_;
my $self = {};
bless $self, $class;
}
package main;
use strict;
use warnings;
my $object = MyClass -> new;
$object -> {var} = "fleeg";
print "Undef fiddle was: ", $object -> fiddle,"\n";
print "But 'var' was: ", $object -> var,"\n";
This has the same problem, in that changing method names might cause things to break. However it has the advantage that you can handle 'invalid' method calls however you like.
But really - explicit 'get' and 'set' methods are better choices for most use-cases.
You do this by providing proper getter/setter methods that wrap around your class/instance variables. The internals should never be accessed directly, particularly from outside of the class itself (it's wise to not do so within the class either, except for the actual method that maintains that specific attribute. Here's a very basic example:
use warnings;
use strict;
package A;
sub new {
my ($class, %args) = #_;
my $self = bless {}, $class;
$self->x($args{x});
$self->y($args{y});
return $self;
}
sub x {
my ($self, $x) = #_;
$self->{x} = $x if defined $x;
return $self->{x} // 1;
}
sub y {
my ($self, $y) = #_;
$self->{y} = $y if defined $y;
return $self->{y} // 2;
}
package main;
my $obj = A->new(x => 5, y => 3);
print $obj->x ."\n";
print $obj->y ."\n";
Now, you could just as easily do print $obj->{x}, but that's where your problem is. What happens when the code is much more complicated than this, and for some reason you want to change the x attribute name to foo, but retain the x() method? $obj->{x} will now be undef as its never set.
Always use the provided methods for accessing attributes of a class/object. Encapsulation such as this is a staple of OO programming.
I have come across an odd problem in one of my Perl scripts. I have a Perl object. Within a certain scope I want one of the objects attributes to be changed, but I want the attribute to be restored to it's old value after it leaves the scope.
Example:
my $object = Object->new('name' => 'Bob');
{
# I know this doesn't work, but it is the best way
# I can represent what I amd trying to do.
local $object->name('Lenny');
# Prints "Lenny"
print $object->name();
}
# Prints "Bob"
print $object->name();
Is there a way to achieve something like this?
This might not be as much encapsulation as you were asking for, but you can local-ize an attribute of a hash. This outputs "CarlLennyCarl"
sub Object::new { bless { _name => $_[1] }, $_[0] } }
sub Object::name { $_[0]->{_name} }
my $obj = Object->new("Carl");
print $obj->name;
{
local $obj->{_name} = "Lenny";
print $obj->name;
}
print $obj->name;
You could also local-ize the entire method. This also outputs "CarlLennyCarl":
sub Object::new { bless { _name => $_[1] }, $_[0] } }
sub Object::name { $_[0]->{_name} }
my $obj = Object->new("Carl");
print $obj->name;
{
local *Object::name = sub { "Lenny" };
print $obj->name;
}
print $obj->name;
I was completely misunderstanding what was occurring there. You cannot use local on subroutine calls, that is the issue you are having.
Lets use a code example from one that I know works and try to explain what eval is actually doing.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Cwd;
print getcwd() . "\n";
eval{
local #INC = ('/tmp');
require 'test.pl';
print 'local: ' . Dumper(\#INC);
};
print Dumper(\#INC);
That works because I am modifying a variable, not calling on another subroutine to modify my variable.
In order for it to work as you are expecting, you would have to create a deep copy of the object to modify in local scope or something of the sort. (which I'm pretty sure is what is occurring in the first place)
local creates scope for the given brackets, eval, OR file (your problem there)
If you were able to access the elements directly without the method call (bad practice IMHO) you would likely be able to localize the scope of that element in the object.
Example:
name.pm:
package name;
use strict;
use warnings;
{
sub new {
my ($class,$name) = #_;
my $self = bless {}, $class;
$self->{'name'} = $name if defined $name;
return $self;
}
sub name
{
my ($self,$name) = #_;
$self->{'name'} = $name if defined $name;
return $self->{'name'};
}
}
index.pl:
#!/usr/bin/perl -w
use strict;
use warnings FATAL => 'all';
use name;
my $obj = name->new('test');
print $obj->{'name'} . "\n";
{
local $obj->{'name'} = 'test2';
print $obj->{'name'} . "\n";
}
print $obj->{'name'} . "\n";
Currently, during debugging, I tend to insert
carp Dumper \#foo, \%bar;
statements into my code, and regularly run into the problem that Carp and Data::Dumper are not imported in the current package. To avoid this problem, I defined the following sub in a central file that's always included:
sub main::bla {
use Carp; use Data::Dumper;
carp Dumper #_;
}
So now I can do
main::bla \#foo, \%bar;
everywhere, but the main:: annoys me. Is there something like a global package that's always imported, in any package, automatically (just like built-ins practically are imported everywhere)?
You could just call it
::bla( \#foo, \%bar );
In earlier days, I used to put util functions in a Ut package. And that was fine, but I noticed that my code wasn't really as modular as I thought of it. Each module that depended on Ut functions being there could only succeed if somebody took the trouble to build that namespace.
In the end, I considered use and require statements as simply documenting dependencies. There are more flexible ways to change what code is called by library modules, rather than changing their implementation in main.
For example, you could do this in a single use statement.
use Ut blah => sub {
use Carp;
use Data::Dumper;
carp Dumper #_;
};
And define the import:
sub import {
shift; # It's just me
my ( $name, $impl ) = #_;
if ( $name eq 'blah' && ref( $impl ) eq 'CODE' ) {
*blah = $_[1];
}
...
}
I still use the ut pattern, when I'm developing a lot of code at once. But I don't mind writing
ut:dump_var( $var )
as opposed to saving 3-4 characters (because sometimes I call it U::).
Now, it appears that you don't want to do this long term, and dumping out your variables is a useful thing for development. If you really want to do this, Smart::Comments does it like so:
### #foo
### %bar
All it takes is one use statement.
use Smart::Comments;
Maybe just better to make another Package with Export and needed things?
Like, MyTest.pm:
package MyTest;
use strict;
use Carp;
use Data::Dumper;
use base qw( Exporter );
our #EXPORT = qw(
debug
)
sub debug {
carp Dumper #_;
}
1;
So you can then just write in your script:
use MyTest;
debug {a => 'b', c => 'd' }
Fun fact: Some symbols are magic in that they always refer to their values in the main package. You can assign subroutines to these symbols that and they will be visible in any package.
{
package Foo;
# special names _ ARGV ARGVOUT ENV INC SIG STDERR STDIN STDOUT
sub ENV { print "In main::ENV ...\n" }
sub STDIN { print "In main::STDIN ...\n" }
sub _ { print "In main::_\n" }
# names that begin with ^ + upper case letter, or all digits
*{^T} = sub { scalar localtime };
*{^Gmtime} = sub { scalar gmtime };
*43 = sub { 42 };
use Data::Dumper;
*{^D} = \&Data::Dumper::Dumper;
}
{
package Bar;
&ENV;
STDIN();
print "The time is ", &^T, "\n";
print "In London it is ", &{^Gmtime}, "\n";
print "The answer is ", &43, "\n";
print "\#foo is ", &^D( \#foo );
}
None of this is recommended, unless you want to drive the next person who maintains your code insane.
So I am toying with some black magic in Perl (eventually we all do :-) and I am a little confused as to exactly how I am supposed to be doing all of this. Here is what I'm starting with:
use strict;
use warnings;
use feature ':5.10';
my $classname = 'Frew';
my $foo = bless({ foo => 'bar' }, $classname);
no strict;
*{"$classname\::INC"} = sub {
use strict;
my $data = qq[
package $classname
warn 'test';
sub foo {
print "test?";
}
];
open my $fh, '<', \$data;
return $fh;
};
use strict;
unshift #INC, $foo;
require $foo;
use Data::Dumper;
warn Dumper(\#INC);
$classname->foo;
I get the following errors (depending on whether my require line is commented out):
With require:
Recursive call to Perl_load_module in PerlIO_find_layer at crazy.pl line 16.
BEGIN failed--compilation aborted.
without:
$VAR1 = [
bless( {
'foo' => 'bar'
}, 'Frew' ),
'C:/usr/site/lib',
'C:/usr/lib',
'.'
];
Can't locate object method "foo" via package "Frew" at crazy.pl line 24.
Any wizards who know some of this black magic already: please answer! I'd love to learn more of this arcana :-)
Also note: I know that I can do this kind of stuff with Moose and other lighter helper modules, I am mostly trying to learn, so recommendations to use such-and-such a module will not get my votes :-)
Update: Ok, I guess I wasn't quite clear originally with my question. I basically want to generate a Perl class with a string (that I will manipulate and do interpolation into) based on an external data structure. I imagine that going from what I have here (once it works) to that shouldn't be too hard.
Here is a version which works:
#!/usr/bin/perl
use strict;
use warnings;
my $class = 'Frew';
{
no strict 'refs';
*{ "${class}::INC" } = sub {
my ($self, $req) = #_;
return unless $req eq $class;
my $data = qq{
package $class;
sub foo { print "test!\n" };
1;
};
open my $fh, '<', \$data;
return $fh;
};
}
my $foo = bless { }, $class;
unshift #INC, $foo;
require $class;
$class->foo;
The #INC hook gets the name of the file (or string passed to require) as the second argument, and it gets called every time there is a require or use. So you have to check to make sure we're trying to load $classname and ignore all other cases, in which case perl continues down along #INC. Alternatively, you can put the hook at the end of #INC. This was the cause of your recursion errors.
ETA: IMHO, a much better way to achieve this would be to simply build the symbol table dynamically, rather than generating code as a string. For example:
no strict 'refs';
*{ "${class}::foo" } = sub { print "test!\n" };
*{ "${class}::new" } = sub { return bless { }, $class };
my $foo = $class->new;
$foo->foo;
No use or require is necessary, nor messing with evil #INC hooks.
I do this:
use MooseX::Declare;
my $class = class {
has 'foo' => (is => 'ro', isa => 'Str', required => 1);
method bar() {
say "Hello, world; foo is ", $self->foo;
}
};
Then you can use $class like any other metaclass:
my $instance = $class->name->new( foo => 'foo bar' );
$instance->foo; # foo-bar
$instance->bar; # Hello, world; foo is foo-bar
etc.
If you want to dynamically generate classes at runtime, you need to create the proper metaclass, instantiate it, and then use the metaclass instance to generate instances. Basic OO. Class::MOP handles all the details for you:
my $class = Class::MOP::Class->create_anon_class;
$class->add_method( foo => sub { say "Hello from foo" } );
my $instance = $class->new_object;
...
If you want to do it yourself so that you can waste your time debugging something, perhaps try:
sub generate_class_name {
state $i = 0;
return '__ANON__::'. $i++;
}
my $classname = generate_class_name();
eval qq{
package $classname;
sub new { my \$class = shift; bless {} => \$class }
...
};
my $instance = $classname->new;
For a simple example of how to do this, read the source of Class::Struct.
However, if I needed the ability to dynamically build classes for some production code, I'd look at MooseX::Declare, as suggested by jrockway.
A Perl class is little more than a data structure (usually a hashref)
that has been blessed into a package in which one or more class
methods are defined.
It is certainly possible to define multiple package namespaces in one
file; I don't see why this wouldn't be possible in an eval construct
that is compiled at run-time (see perlfunc for the two different
eval forms).
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use Data::Dumper;
eval q[
package Foo;
sub new {
my ( $class, %args ) = #_;
my $self = bless { %args }, $class;
return $self;
}
1;
];
die $# if $#;
my $foo = Foo->new(bar => 1, baz => 2) or die;
say Dumper $foo;