Type coercion in Perl6 class attribute - type-conversion

Like most things in Perl5, there are many ways to create a class that supports custom type coercions for its attributes. Here's a simple one, from an array reference to a hash:
#!/usr/bin/env perl
package Local::Class {
use Moo;
use Types::Standard qw( HashRef ArrayRef );
has set => (
is => 'ro',
coerce => 1,
isa => HashRef->plus_coercions(
ArrayRef, sub { return { map { $_ => 1} #{$_[0]} } },
),
);
}
my $o = Local::Class->new({ set => [qw( a b b c )] });
# $o->set now holds { a => 1, b => 1, c => 1}
I've been trying to port something like this to Perl6, where it seems like what I need is a way to coerce an Array to a SetHash. So far, the only way I've been able to do that is like this:
#!/usr/bin/env perl6
class Local::Class {
has %.set;
## Wanted to have
# has %.set is SetHash;
## but it dies with "Cannot modify an immutable SetHash"
submethod TWEAK (:$set) {
self.set = $set.SetHash;
}
}
my $o = Local::Class.new( set => [< a b b c >] );
# $o.set now holds {:a, :b, :c}
But this doesn't seem to me to be the right way to do it, at the very least for the tiny detail that passing an odd-numbered list to the constructor makes the script die.
So, how is this done in Perl6? What are the recommended ways (because I'm sure there's more than one) to implement custom type coercions for class attributes?

TWEAK runs after the object has been intialized by BUILD, and that's where providing an odd-numbered array will blow up.
Move the coercion to BUILD time, and things should work as expected:
class Local::Class {
has %.set;
submethod BUILD (:$set) {
%!set := $set.SetHash;
}
}
It would be nice if you could use a coercing SetHash() parameter type in combination with automatic attribute initialization, but this will fail as the sigil is part of the attribute's name, and the parameter cannot be %-sigilled if you want to accept non-associative types.
However, it works fine if you use a $-sigilled attribute instead:
class Local::Class {
has $.set;
submethod BUILD (SetHash() :$!set) {}
}
As #smls points out, the following variant
class Local::Class {
has %.set is SetHash;
submethod BUILD (:$set) {
%!set = $set.SetHash;
}
}
probably should work as well instead of dying with Cannot modify an immutable SetHash.
As a workaround for SetHash not being assignable, you could use
class Local::Class {
has %.set is SetHash;
submethod BUILD (:$set) {
%!set{$set.SetHash.keys} = True xx *;
}
}

Related

Declare struct in class Perl

I want declare struct in of mi class in perl.In case anyone does not know this module: http://perldoc.perl.org/Class/Struct.html
And this is my code:
package object;
sub new{
my $class = shift;
struct( 'object', { hashMap => '%', array => '#' });
my $self = {
'object::hashMap' => shift ,
'object::array' => shift ,
_toret => shift // 0,
};
$self->{_toret} = new object;
return bless $self, $class;
}
if i want get my hasMap from struct, i should a object to call this
struct, but I don't want to declare it in a function, because i would
lose the data every time I refer the function.And that's why I declare it in class. The problem is that when i do the function, to get the hasMap this gives me error and tells me that it cann´t be referenced to hashMap
sub get{
my($self) =#_;
return $self->{_toret}->hashMap;
}
i hope that you can help me. thanks!!
I think you're overcomplicating this massively. The struct() function should be called from within the top-level package code, not from within the constructor (calling struct() creates a constructor called new() for you). And calling new() from within new() seems like a recipe for disaster.
You've described what doesn't work, but you haven't really described what you're trying to achieve. I think that you want this (but, without more details, I really can't be sure).
# Object is a terrible name for a class.
# But if you insist on using it, at least capitalise the name.
# Lowercase names are reserved for pragmata in Perl.
package Object;
use Class::Struct;
struct( hashMap => '%', array => '#' );
1;
You can then use your class like this:
use Object;
my $obj = Object->new(
hashMap => { foo => 1, bar => 2, baz => 3},
array => [ 0 .. 10]
);
say $obj->hashMap->{foo}; # prints 1
say $obj->array->[10]; # prints 10
If that's not what you want, you need to explain a lot more about what you are trying to do.

Changing writer prefix when (is => “rwp”)

If I want to change write protected attribute ie.
use Moops;
class foo {
has attr => (is => "rwp");
}
one have to use _set_attr().
Is it possible to change that to _attr() without using explicit writer?
Tried use MooseX::::AttributeShortcuts -writer_prefix => 'prefix'; but it did not work.
No, you need to do that yourself by setting the writer.
TLDR: At the bottom is a monkey-patch to do it anyway.
The Moops docs say (emphasys mine):
Moops uses MooseX::MungeHas in your classes so that the has keyword
supports some Moo-specific features, even when you're using Moose or
Mouse. Specifically, it supports is => 'rwp', is => 'lazy', builder =>
1, clearer => 1, predicate => 1, and trigger => 1.
Now let's go look at Moo. In the has section of the doc, it says (emphasys mine):
rwp stands for "read-write protected" and generates a reader like ro,
but also sets writer to _set_${attribute_name} for attributes that are
designed to be written from inside of the class, but read-only from
outside. This feature comes from MooseX::AttributeShortcuts.
Ok, on to MooseX::AttributeShortcuts:
Specifying is => 'rwp' will cause the following options to be set:
is => 'ro'
writer => "_set_$name"
However, this is just where it was inspired. It is actually implemented in Moo in Method::Generate::Accessor1.
} elsif ($is eq 'rwp') {
$spec->{reader} = $name unless exists $spec->{reader};
$spec->{writer} = "_set_${name}" unless exists $spec->{writer};
} elsif ($is ne 'bare') {
And even more actually, that is also not where it is done in Moops. In fact, that happens in MooseX::MungeHas, which Moops uses, but only if the caller is not Moo:
push #code, ' if ($_{is} eq q(rwp)) {';
push #code, ' $_{is} = "ro";';
push #code, ' $_{writer} = "_set_$_" unless exists($_{writer});';
push #code, ' }';
Looks pretty clear. It's in generated code. The below solution might work if it uses only Moo, but I don't know how to force that.
You are indeed able to change that in Moo by hooking into Moo's Method::Generate::Accessor using Class::Method::Modifiers and adding a bit of logic in an around modifier to generate_method. This does not work works for Moops as long as there is no Moose-stuff involved.
use Moops;
BEGIN {
require Method::Generate::Accessor; # so it's in %INC;
require Class::Method::Modifiers;
Class::Method::Modifiers::around( 'Method::Generate::Accessor::generate_method' => sub {
my $orig = shift;
# 0 1 2 3 4
# my ($self, $into, $name, $spec, $quote_opts) = #_;
if ($_[3]->{is} eq 'rwp') {
$_[3]->{writer} = "_explicitly_set_$_[2]" unless exists $_[3]->{reader};
}
$orig->(#_);
});
}
class Foo {
has attr => ( is => "rwp" );
}
use Data::Printer;
my $foo = Foo->new( attr => 1 );
p $foo;
Output:
Foo {
Parents Moo::Object
public methods (2) : attr, new
private methods (1) : _explicitly_set_attr
internals: {
attr 1
}
}
1) I found that using grep.cpan.me.

programmatically creating a Moose class at run time

I've been playing around with this code:
package Foo;
use Moose;
package main;
my $PACKAGE = "Foo";
{
no strict 'refs';
my $has = *{"${PACKAGE}::has"}{CODE};
my $with = *{"${PACKAGE}::with"}{CODE};
# Add a instance member to class $PACKAGE
$has->("bar", is => "rw", required => 1);
# Add a role to class $PACKAGE
$with->("some::role");
}
# Create an instance of $PACKAGE:
$PACKAGE->new(); # error: attribute 'bar' is required means we were successful
This allows me to create a Moose class at run-time, i.e. add instance members to a class, add roles, etc.
My question is: how can I import Moose into package $PACKAGE?
I know I can do this with eval: eval "package $PACKAGE; use Moose"; but I'm wondering if there is a solution along the lines of Moose->import(... $PACKAGE ...).
i.e., a way without using eval. Or is there a completely different way of creating and modifying Moose classes at run time?
You probably want to take a look at Moose::Meta::Class and its create method:
my $class = Moose::Meta::Class->create('Foo',
attributes => [attr => Moose::Meta::Attribute->new(is => 'ro'), ...],
roles => [...],
methods => {...},
superclasses => [...],
);
# Edit: Adding an attribute and method modifiers:
$class->add_attribute(otherattr => (is => 'ro'));
$class->add_around_method_modifier(methodname => sub { ... });
Moose::Meta::Class is a subclass of Class::MOP::Class, so you might want to peek into that one as well. With the above, you can specify roles, superclasses, attributes and methods, or you can first create and then add them via the MOP; whatever fits best.
For the attributes you'll want the Moose kind, which means Moose::Meta::Attribute objects. The constructor to that object is basically the same as using has.
You may want to use Class::MOP, see for example https://metacpan.org/module/Moose::Manual::MOP#ALTERING-CLASSES-WITH-THE-MOP
or
https://metacpan.org/module/Class::MOP::Class#SYNOPSIS
Call extends, with, has, before, after, around, override and augment in the Moose package instead of the ones exported by Moose, and pass the meta object of the class you are creating as an additional first argument.
use strict;
use warnings;
use feature qw( say );
use Moose qw( );
{ # Create MyClass on the fly.
my $meta = Moose->init_meta( for_class => 'MyClass' );
# Moose::with( $meta, 'MyRole' );
Moose::has( $meta, foo => (
is => 'ro',
));
}
say MyClass->new( foo => "Foo" )->foo; # Foo

How can I flexibly add data to Moose objects?

I'm writing a module for a moose object. I would like to allow a user using this object (or myself...) add some fields on the fly as he/she desires. I can't define these fields a priori since I simply don't know what they will be.
I currently simply added a single field called extra of type hashref which is is set to rw, so users can simply put stuff in that hash:
# $obj is a ref to my Moose object
$obj->extra()->{new_thingie}="abc123"; # adds some arbitrary stuff to the object
say $obj->extra()->{new_thingie};
This works. But... is this a common practice? Any other (possibly more elegant) ideas?
Note I do not wish to create another module the extends this one, this really just for on-the-fly stuff I'd like to add.
I would probably do this via native traits:
has custom_fields => (
traits => [qw( Hash )],
isa => 'HashRef',
builder => '_build_custom_fields',
handles => {
custom_field => 'accessor',
has_custom_field => 'exists',
custom_fields => 'keys',
has_custom_fields => 'count',
delete_custom_field => 'delete',
},
);
sub _build_custom_fields { {} }
On an object you'd use this like the following:
my $val = $obj->custom_field('foo'); # get field value
$obj->custom_field('foo', 23); # set field to value
$obj->has_custom_field('foo'); # does a specific field exist?
$obj->has_custom_fields; # are there any fields?
my #names = $obj->custom_fields; # what fields are there?
my $value = $obj->delete_custom_field('foo'); # remove field value
A common use-case for stuff like this is adding optional introspectable data to exception and message classes.
If you haven't made the class immutable (there is a performance penalty for not doing that, in addition to my concerns about changing class definitions on the fly), you should be able to do that by getting the meta class for the object (using $meta = $object->meta) and using the add_attribute method in Class::MOP::Class.
#!/usr/bin/perl
package My::Class;
use Moose;
use namespace::autoclean;
package main;
my $x = My::Class->new;
my $meta = $x->meta;
$meta->add_attribute(
foo => (
accessor => 'foo',
)
);
$x->foo(42);
print $x->foo, "\n";
my $y = My::Class->new({ foo => 5 });
print $y->foo, "\n";
Output:
42
5
Just in case you want to add a method to an object and not to the whole class then have a look at something like MooseX::SingletonMethod.
E.g.
use 5.012;
use warnings;
{
package Foo;
use MooseX::SingletonMethod;
sub bar { 'bar' } # method available to all objects
}
my $foo = Foo->new;
$foo->add_singleton_method( baz => sub { 'baz!' } );
$foo->baz; # => baz!
So in above the method baz is only added to the object $foo and not to class Foo.
Hmmm... I wonder if I could implement a MooseX::SingletonAttribute?
Some previous SO answer using MooseX::SingletonMethod:
How do you replace a method of a Moose object at runtime?
How do I make a new Moose class and instantiate an object of that class at runtime?
And also this blog post maybe of use and/or interest: Easy Anonymous Objects
/I3az/
Even if it's not a good pratice to modify a class at runtime, you can simply make the meta-class mutable, add the attribute(s) and make class immutable again:
$ref->meta->make_mutable ;
$ref->meta->add_attribute($attr_name,%cfg) ;
$ref->meta->make_immmutable ;

How do I create an instance of value from the attribute's meta object with Moose?

I'm working on a serialization tool using Moose to read and write a file that conforms to a nonstandard format. Right now, I determine how to load the next item based on the default values for the objects in the class, but that has its own drawbacks. Instead, I'd like to be able to use information in the attribute meta-class to generate a new value of the right type. I suspect that there's a way to determine what the 'isa' restriction is and derive a generator from it, but I saw no particular methods in Moose::Meta::Attribute or Class::MOP::Attribute that could help me.
Here's a bit further of an example. Let's say I have the following class:
package Example;
use Moose;
use My::Trait::Order;
use My::Class;
with 'My::Role::Load', 'My::Role::Save';
has 'foo' => (
traits => [ 'Order' ],
isa => 'Num',
is => 'rw',
default => 0,
order => 1,
);
has 'bar' => (
traits => [ 'Order' ],
isa => 'ArrayRef[Str]',
is => 'rw',
default => sub { [ map { "" } 1..8 ] }
order => 2,
);
has 'baz' => (
traits => [ 'Order' ],
isa => 'Custom::Class',
is => 'rw',
default => sub { Custom::Class->new() },
order => 3,
);
__PACKAGE__->meta->make_immutable;
1;
(Further explanation: My::Role::Load and My::Role::Save implement the serialization roles for this file type. They iterate over the attributes of the class they're attached to, and look at the attribute classes for an order to serialize in.)
In the My::Role::Load role, I can iterate over the meta object for the class, looking at all the attributes available to me, and picking only those that have my Order trait:
package My::Role::Load;
use Moose;
...
sub load {
my ($self, $path) = #_;
foreach my $attribute ( $self->meta->get_all_attributes ) {
if (does_role($attribute, 'My::Trait::Order') ) {
$self->load_attribute($attribute) # do the loading
}
}
}
Now, I need to know the isa of the attribute that the meta-attribute represents. Right now, I test that by getting an instance of it, and testing it with something that's kind of like this:
use 5.010_001; # need smartmatch fix.
...
sub load_attribute {
my ($self, $attribute, $fh) = #_;
my $value = $attribute->get_value($self); # <-- ERROR PRONE PROBLEM HERE!
if (ref($value) && ! blessed($value)) { # get the arrayref types.
given (ref($value)) {
when('ARRAY') {
$self->load_array($attribute);
}
when('HASH') {
$self->load_hash($attribute);
}
default {
confess "unable to serialize ref of type '$_'";
}
}
}
else {
when (\&blessed) {
confess "don't know how to load it if it doesn't 'load'."
if ! $_->can('load');
$_->load();
}
default {
$attribute->set_value($self, <$fh>);
}
}
}
But, as you can see at # <-- ERROR PRONE PROBLEM HERE!, this whole process relies on there being a value in the attribute to begin with! If the value is undef, I have no indication as to what to load. I'd like to replace the $attribute->get_value($self) with a way to get information about the type of value that needs to be loaded instead. My problem is that the docs I linked to above for the Class::MOP::Attribute and the Moose::Meta::Attribute don't seem to have any way of getting at the type of object that the attribute is supposed to get.
The type information for an attribute is basically what I'm trying to get at.
(Note to future readers: the answer here got me started, but is not the final solution in of itself. You will have to dig into the Moose::Meta::TypeConstraint classes to actually do what I'm looking for here.)
Not sure I follow what you are after and perhaps Coercions might do what you want?
However to get the attributes isa:
{
package Foo;
use Moose;
has 'bar' => ( isa => 'Str', is => 'rw' );
}
my $foo = Foo->new;
say $foo->meta->get_attribute('bar')->type_constraint; # => 'Str'
/I3az/
Out of curiosity why not use/extend MooseX::Storage? It does Serialization, and has for about two and a half years. At the very least MooseX::Storage will help you by showing how a (well tested and production ready) serialization engine for Moose is written.
I'm not quite sure I understand (perhaps you can include some pseudocode that demonstrates what you are looking for), but it sounds like you could possibly get the behaviour you want by defining a new attribute trait: set up your attribute so that a bunch of methods on the class delegate to the attribute's object (isa => 'MySerializer', handles => [ qw(methods) ]).
You might possibly also need to subclass Moose::Meta::Class (or better, add a role to it) which augments the behaviour of add_attribute().
Edit: If you look at the source for Moose::Meta::Attribute (specifically the _process_options method), you will see that the isa option is processed by Moose::Util::TypeConstraints to return the actual type to be stored in the type_constraint field in the object. This will be a Moose::Meta::TypeConstraint::Class object, which you can make calls like is_a_type_of() against.
This field is available via the type_constraint method in Moose::Meta::Attribute. See Moose::Meta::TypeConstraint for all the interfaces available to you for checking an attributes's type.