chromatic's recent blog got me curious about the Moose subroutine has
. I was looking at the Moose source code and noticed that inside the has
subroutine, there is a $meta
variable unpacked from @_
. Where does $meta
come from? I've started wading through the various Moose and Class::MOP modules. In many subroutines, it seems that $meta
is commonly found as the first argument in @_
, even though it is not specifically passed to it as an argument.
Edit: Here is the original source code for the has
subroutine:
sub has {
my $meta = shift;
my $name = shift;
Moose->throw_error('Usage: has \'name\' => ( key => value, ... )')
if @_ % 2 == 1;
my %options = ( definition_context => Moose::Util::_caller_info(), @_ );
my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ];
$meta->add_attribute( $_, %options ) for @$attrs;
}
The particular magic you are looking for is in Moose::Exporter. You get the has
method via Moose.pm from this code:
Moose::Exporter->setup_import_methods(
with_meta => [
qw( extends with has before after around override augment )
],
as_is => [
qw( super inner ),
\&Carp::confess,
\&Scalar::Util::blessed,
],
);
Note the "with_meta" option for setup_import_methods
-- it imports those methods into the caller's namespace in a manner which ensures that the first argument passed will be the metaclass object.
The various MooseX modules that extend Moose use Moose::Exporter to import new symbols into the caller's namespace. You can read more about this process in the cookbook, starting at Moose::Cookbook::Extending::Recipe1.
What's actually imported into into your package isn't the named has() subroutine but a closure that inserts the meta object. You can see exactly how this happens with:
$ perl -we'use Data::Dump::Streamer; use Moose; Dump(\&has)'
my ($extra,$sub,@ex_args);
$extra = sub {
package Moose::Exporter;
use warnings;
use strict 'refs';
Class::MOP::class_of(shift @_);
};
$sub = sub {
package Moose;
use warnings;
use strict 'refs';
my $meta = shift @_;
my $name = shift @_;
'Moose'->throw_error(q[Usage: has 'name' => ( key => value, ... )]) if @_ % 2 == 1;
my(%options) = ('definition_context', Moose::Util::_caller_info(), @_);
my $attrs = ref $name eq 'ARRAY' ? $name : [$name];
$meta->add_attribute($_, %options) foreach (@$attrs);
};
@ex_args = ( 'main' );
$CODE1 = sub {
package Moose::Exporter;
use warnings;
use strict 'refs';
my(@curry) = &$extra(@ex_args);
return &$sub(@curry, @_);
};
$CODE1
is the closure itself; above are the variables referenced in it.
molecules
comment in ysth answer:
I'm not sure how the has subroutine gets converted to this closure, but this definitely shows the curried nature of the imported has
Here is (hopefully!) a simple example of how this could be achieved (however I suspect Moose
does it in a much more complex and better way!)
Meta.pm
package Meta;
sub new {
my $class = shift;
bless { @_ }, $class;
}
sub has {
my $meta = shift;
print "Given => @_ \n";
print "meta $_ => ", $meta->{$_}, "\n" for keys %$meta;
}
1;
Import.pm
package Import;
use strict;
use warnings;
use Meta;
# some arbitrary meta info!
our $Meta = Meta->new( a => 'A', b => 'B' );
sub import {
my $caller = caller;
# import 'has' into caller namespace
no strict 'refs';
*{$caller . '::has'} = sub { $Meta->has(@_) };
}
1;
meta_has.pl
use strict;
use warnings;
use Import;
has name => ( is => 'rw', isa => 'Int' );
Now if you run meta_has.pl
you will get:
# Given => name is rw isa Int
# meta a => A
# meta b => B
Hope that helps.
/I3az/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With