Casting scalars to functions and even classes is a pretty simple thing.
Consider the following example in foo.pl:
#!/usr/bin/perl
my $func = shift;
my $result = &$func(@ARGV);
print "$result\n";
sub add {
my ($x, $y) = @_;
return $x + $y;
}
sub subtract {
my ($x, $y) = @_;
return $x - $y;
}
The result of executing this code is fairly simple:
# perl foo.pl add 1 2
3
# perl foo.pl subtract 2 1
1
It's also possible to cast a scalar to a package/class type, allowing more flexibility on which objects or modules are called at run time.
Consider the following two files, foo.pl, bar.pm, baz.pm:
foo.pl:
#!/usr/bin/perl
my $pkg = shift @ARGV;
require "$pkg.pm"; # only load the package specified on the command line
my $class_ref = $pkg->new();
$class_ref->to_string();
bar.pm:
#!/usr/bin/perl
package bar;
sub new {
my $proto = shift;
my $class = ref $proto || $proto;
my $self = {};
bless ($self, $class);
return $self;
}
sub to_string {
my $self = shift;
return "bar says hello!\n";
}
1;
and baz.pm:
#!/usr/bin/perl
package bar;
sub new {
my $proto = shift;
my $class = ref $proto || $proto;
my $self = {};
bless ($self, $class);
return $self;
}
sub to_string {
my $self = shift;
return "baz says hello!\n";
}
1;
Now, execution looks like this:
# perl foo.pl bar
bar says hello!
# perl foo.pl baz
baz says hello!
Of course, there are some severe consequences of doing such practices. But, in my opinion, none of them compare to what else you can do with Perl's "cast-ability."
Consider the following hiearchy: Class linuxFoo is a class, specific to the Linux OS. Class solarisFoo is also a class, specific to the Solaris OS. Class Foo wants to inherit from either, but only does so in accordance to the OS it's currently running from. Here's the code:
linuxFoo.pm:
#!/usr/bin/perl
package linuxFoo;
sub new {
my $proto = shift;
my $class = ref $proto || $proto;
my $self = {};
bless ($self, $class);
return $self;
}
sub to_string {
return "This is Linux-based functionality!\n";
}
1;
solarisFoo.pm:
#!/usr/bin/perl
package solarisFoo;
sub new {
my $proto = shift;
my $class = ref $proto || $proto;
my $self = {};
bless ($self, $class);
return $self;
}
sub to_string {
return "This is Solaris-based functionality!\n";
}
1;
Foo.pm:
#!/usr/bin/perl
package Foo;
sub new {
my $class = shift;
my $os = $^O; # this gets the OS, in case you don't know
# require the correct module, and use it as the parent
my $pkg = $os . "Foo";
require "$pkg.pm";
our @ISA = $pkg;
my $self = $class->SUPER::new();
bless ($self, $class);
return $self;
}
sub to_string {
my $self = shift;
return $self->SUPER::to_string();
}
1;
and test.pl:
#!/usr/bin/perl
use Foo;
my $class_ref = Foo->new();
print $class_ref->to_string();
The result of running this script on two different operating systems (linux and solaris) is as follows:
# perl test.pl # on linux
This is Linux-based functionality!
# perl test.pl # on solaris
This is Solaris-based functionality!
Now, why is this a bad idea??
Creating function references on the fly via string values is always iffy. It requires a full-knowledge of the underlying code in order to correctly identify which methods are required. Though it's possible to abstract this out to some extent (forcing function/method names to use a value which is immutable for the system--e.g. the OS type), it still creates a brittle interface in which to work.
Dynamically creating and using modules is likewise a bit iffy as well. Should a module need to be refactored in the future (or is replaced with a different one), this forces the programmer to update the string which is used to create it.
Either way, the ability to use a string value to execute a function or use a module is tenuous at best. Dynamic inheritance, however, is downright dangerous. Users can take objects and randomly redefine their hierarchical structure however and whenever they want. Languages such as Java and C++ (to name a few) prevent this by statically typing their classes at compile-time. This forces the contracts imposed by inheritance and interfaces (e.g. interface methods are always implemented, inherited methods are always present via the parent, etc.).
Dynamic inheritance throws it all out the window. If you have a Dog object, it can randomly inherit from a Wheel object. Not only this, it can inherit from a Wheel after it's already inherited from a Fire Hydrant. Ok, the rule of odd and bizarre inheritance structures is present in all languages. But redefining the structure at any point in the code, without compile-time checks, makes Perl a fun, but screwy and dangerous language to program in.
...of course...some would say not to do OOP in Perl, which is why I'm transitioning to Python...
No comments:
Post a Comment