July 11, 2008
autobox and ref()
The Perl builtin function ref() “returns a non-empty string if EXPR is a reference, the empty string otherwise”.
ref() is implemented in autobox::Core, but only for ARRAY, HASH and CODE data types. This means that the method call will fail when invoked on a scalar or undef:
use autobox::Core;
my($foo) = ['foo'];
$foo->ref->concat("\n")->print;
#prints 'ARRAY'
my($x) = undef;
$x->ref->concat("\n")->print;
# Fails with 'Can't call method "ref" on an undefined value'
There are two ways we can fix this: either we can use base autobox behaviour and define our own classes to handle SCALAR and UNDEF types, or we can hack the autobox::Core source with a view to submitting a patch to the author.
Let’s look at option number one first:
We define packages to hold our SCALAR and UNDEF implementations:
package MyScalarRef;
use strict;
sub ref ($) { CORE::ref( shift ); }
1;
package MyUndefRef;
use strict;
sub ref ($) { CORE::ref( shift ); }
1;
package main;
use strict;
use autobox SCALAR => 'MyScalarRef'
, UNDEF => 'MyUndefRef' ;
use autobox::Core;
my($foo) = ['foo'];
$foo->ref->concat("\n")->print;
#prints 'ARRAY as before
my($x) = undef;
$x->ref->concat("\n")->print;
# Now prints empty string.
(Note that, since the two packages are implemented identically, we could have pointed both data types at the same namespace. I’ve left them separate for clarity of explanation).
This is fine, and works as advertised, but it’s cumbersome to do this on the off-chance that we might want to call ref() on something other than an array, hash or coderef. A more elegant solution is to patch the autobox::Core source. A diff with autobox-Core-0.6 looks like:
@@ -498,7 +498,17 @@
=cut
+#
+# UNDEF
+#
+
+package autobox::Core::UNDEF;
+# Currently only "ref"
+#
+sub ref ($) { CORE::ref($_[0]); }
+#
+#
#
# SCALAR
#
@@ -508,7 +518,7 @@
# Functions for SCALARs or strings
# "chomp", "chop", "chr", "crypt", "hex", "index", "lc",
# "lcfirst", "length", "oct", "ord", "pack",
-# "q/STRING/", "qq/STRING/", "reverse", "rindex",
+# "q/STRING/", "qq/STRING/", "ref", "reverse", "rindex",
# "sprintf", "substr", "tr///", "uc", "ucfirst", "y///"
# current doesn't handle scalar references - get can't call method chomp on unblessed reference etc when i try to support it
@@ -523,6 +533,7 @@
sub length ($) { CORE::length($_[0]); }
sub ord ($) { CORE::ord($_[0]); }
sub pack ($;@) { CORE::pack(@_); }
+sub ref ($) { CORE::ref($_[0]); }
sub reverse ($) { CORE::reverse($_[0]); }
sub rindex ($@) { CORE::rindex($_[0], $_[1], @_[2.. $#_]); }
sub sprintf ($@) { CORE::sprintf($_[0], $_[1], @_[2.. $#_]); }
i.e. we have added a call to CORE::ref in the existing SCALAR package, and added a new UNDEF package which currently contains only our ref() implementation.
How does this perform?
package main;
use strict;
use autobox::Core;
my($foo) = ['foo'];
$foo->ref->concat("\n")->print;
#prints 'ARRAY'
my($x) = undef;
$x->ref->concat("\n")->print;
## Fails with 'Can't call method "ref" on an undefined value'
So what has gone wrong here?
autobox::Core over-rides the import() method to associate data types with a corresponding namespace that provides the implementation. As of version 0.6, it looks like this:
sub import {
shift->SUPER::import(DEFAULT => 'autobox::Core::', @_);
}
This means that the autobox DEFAULT data types are associated with autobox:Core: these are SCALAR, ARRAY, HASH and CODE. Therefore, by default, our UNDEF definition is never actually associated with autobox::Core and hence never actually called. We need a further modification to import():
sub import {
shift->SUPER::import(DEFAULT => 'autobox::Core::'
,UNDEF=>'autobox::Core::'
,@_);
}
This now works as expected:
package main;
use strict;
use autobox::Core;
my($foo) = ['foo'];
$foo->ref->concat("\n")->print;
#prints 'ARRAY as before
my($x) = undef;
$x->ref->concat("\n")->print;
# Now prints empty string.