Saturday 15 February 2014

Perl is Cool 2, Reflection

Perl sports some excellent reflection capabilities, indeed it does not need a Reflection API as Reflection is built into the Language itself. Let's take a fast look at some of these features:

eval

Same as JavaScript, Perl provides an eval function that allows for the compilation of new code at runtime. You could be tempted to use it in many occasions (creating new Objects, accessing Methods or Data Fields known at runtime), but most times this is unnecessary and you can use a more straightforward approach.

Get Object Type

You can get the type of an object (the Package used to bless a reference) with ref.

Object Creation

Notice that ref returns a string (in contrast with .Net or Java, where .GetType or .getClass return an instance of Type or Class respectively, a special type that exists just for that, representing types). Being just a string could seem to pose some limitations, but it doesn't, cause perl allows you to use a string to reference a package/class and invoke methods on it. This means that you can create objects based on a string provided at runtime:

my $type = "Person";
$type->New("Iyan");

If you're writing Old School Object Oriented Perl you're using just that in your constructors to bless your references:

sub Person{
my ($class, $name) = @_;
my $self = {
name => $name
};
bless $self, $class;
return $self;
...
Method Invokation

Likewise, you can use a string to invoke a method

my $methodName = "SayHi";
$p1->$methodName();

Obviously you can combine the 2 features above, doing something like this:

my $class = "Person";
say $class->New("Anton")->$method();
Check whether an object has a method
You can do this directly on your "class instances" by means of UNIVERSAL::can , or by checking if the corresponding module contains that function:
say "p1 " . ($p1->can("SayHi") ? "CAN" : "CAN NOT") . " sayHi";

say "Person " .  (defined(Person::SayHi) ? "CAN" : "CAN NOT") . " SayHi";
Get Current Method Name
You can use the caller function to traverse the current stack. It returns an array of strings, one of the most useful uses is doing a print (caller(0))[3]; to get the name of the currently executing function.

Get Package Name
You can get the name of the current module with __PACKAGE__

Get File Name
You can get the path of the current module with __FILE__

Expand your objects
Once you've learnt that the minimalistic (but incredibly powerful) support for OOP in Perl revolves above blessing hash references (yes, I know you can bless other objects, but it's pretty uncommon), you can draw similarities with JavaScript, and start to think of your Perl objects as objects that can be expanded at will with new fields and methods. Well, this is partially correct.

You'll have no problem in adding new data fields to your instances:
my $p1 = Person->New(); #where Person::New does not create any "color" property
$p1->{color} = "red";
but adding a method is a different story. This is cause the method look up mechanism in Perl is pretty different from JavaScript's one. Invoking a method in JavaScript will mean retrieving the corresponding function referenced by that object property (directly or through the prototype chain) and calling it with the corresponding "dynamic this". In Perl, when a method invokation is done on an object (a blessed reference), the interpreter will search that function in the package that was used to bless that object and will invoke it passing over to it the object as first argument.
So If you set an object property to a function reference, invoking it as if it were a normal method will fail (with an error like this: Can't locate object method "SayBye" via package "Person" , as that function is not in the Package itself, but you can use a sort of workaround:
#adding a method to an object
$p1->{SayBye} = sub{
	my $self = shift;
	return $self->{"name"} . " is saying Bye";
};

#the method look up does not work, as it's trying to find the method in the Person package, and it's not there
#Runtime error: Can't locate object method "SayBye" via package "Person" 
#print $p1->SayBye() . "\n";

#so we have to do it this way, by accessing the coderef in the object
print $p1->{SayBye}->($p1) . "\n";

I think we could do the above workaround much more elegant by means of AUTOLOAD, but I haven't had time yet to fiddle with it.

No comments:

Post a Comment