On Thu, Jan 15, 2009 at 6:52 AM, Howe, Tom (IT)
<tom.h...@morganstanley.com> wrote:

...

>
> It seems like I need to define the subtype otherwise the coercion complains.
>
> If I remove the subtype 'ArrayRef[Thing]' I get error of type:
>
> Cannot find type 'ArrayRef[Thing]', perhaps you forgot to load it. at
> ...
>        
> Moose::Util::TypeConstraints::_install_type_coercions('ArrayRef[Thing]', 
> 'ARRAY(0x8ccfffc)') called at
> ...
>        Moose::Util::TypeConstraints::coerce('ArrayRef[Thing]', 
> 'ArrayRef[HashRef]', 'CODE(0x8cb9418)') called at


Yeah sorry my bad there. You'll need to move the coercion to just
*after* you define the type for the first time.

has foo_list ( isa => 'ArrayRef[Foo]" ...);
coerce 'ArrayRef[Foo]' => from ArrayRef => via { ... };

otherwise yes you'll need to declare the subtype explicitly.

>>
>> > It should ideally check before it coerces...
>> >
>> > Anything I can do to improve on this?
>>
>> What are you hoping it will check?
>>
>> Moose doesn't do any kind of "Deep Coercion" because the edge
>> cases become far to messy to handle in an elegant way ... or
>> at least so far as anybody has thought of. What you are doing
>> seems to be the right solution ... with my comments about the
>> "subtype ArrayRef[Thing]"
>> statment being optionally implicit.
>
>
>
> I guess in my mind it would work like this:
>
> Does the value you are trying to set match the 'where' in
> the type definition?
>  Yes -> store it
>  No  -> Does it match a coercion entry point?
>        Yes -> coerce it, then store it.
>        No  -> error.
>
> Whereas Moose coerces first then applies the type check.

Yes one might assume it works that way. I just had to write a test to
prove to myself it didn't. That seems broken, perhaps someone else who
knows more about the TypeConstraints and the theory behind the can
explain why that doesn't work that way.

> So in my example above, the coercion will be applied to every
> Array you try to set, even if it is already matches the checks
> in the type def. Given that the coercion might be expensive this
> isnt ideal.
> Is there a way to access the where=> sub from the type def from
> within the coerce via=> sub of the coerce?
>
> So you could have something like..
>
> Coerce => 'ArrayRef[Thing]'
>  via => {
>    # check input type and only coerce if not matching type
>    if ( 'ArrayRef[Thing]'->where($_) ) {
>      return $_;
>    } else {
>      # fails type, do coerce.
>      return [ map {  ..  } @{$_} ];
>    }
>  }

Well you can do something like

coerce  'ArrayRef[Thing]' => from ArrayRef => via {  [  map {
blessed($_) eq 'Thing' ? $_ : Thing->new($_) } @$_ ] };


Where Thing->new() is the possibly expensive conversion routine.

>>
>> > Also, I'd like to be able to declare something like
>> >
>> > has 'foo' => ( is=>'rw', delegate=> sub { Foo->instance }, handles
>> > =>[qw/x y z/] )
>> >
>> > Where, if no value is passed in to foo() on construction,
>> the accessor created for foo() will always trigger the
>> delegate sub but will not store anything in the object in the
>> way default does.
>> >
>> >
>> > I tried this ..
>> >
>> > has 'foo' => ( is=>'rw', isa=>'Object', handles=> [qw/meth1 meth2
>> > meth3/]);
>> >
>> > around 'foo' => sub {
>> >  my ($next,$self,@args) = @_;
>> >  if (@args) {
>> >    return $self->$next(@args);
>> >  } else {
>> >    return $self->$next() || Foo->instance();  } };
>> >
>> > But got error:
>> >
>> > Cannot delegate meth1 to meth1 because the value of foo is
>> not defined...
>> >
>> >
>> > Is there some way to do this?
>>
>> Uh ... not if I'm understanding what you're asking for properly.
>>
>> You want an attribute accessor to sometimes store and Object,
>> and sometimes just call a Class Method on some random class
>> if the attribute isn't set? You'll have to write that yourself:
>>
>> has foo => ( is => 'rw', predicate => '_has_foo', handles =>
>> [qw(one two three)] );
>>
>> around [qw(one two three)] => sub {
>>     my ($next, $self) = (shift, shift);
>>     return Foo->instance->$next(@_) unless $self->_has_foo;
>>     return $self->$next(@_);
>> };
>>
>> You could probably wrap this up in a MooseX module with some
>> sugar if you find your using it a lot, but I don't understand
>> why you'd want to do that in the first place.
>>
>> -Chris
>
>
>
> I tried your example above but am getting the same error as before:
>
> "Cannot delegate meth1 to meth1 because the value of foo is not defined at"
>
> It seems that if you don't set the attribute, it wont be happy about using 
> delgation to it.

Ahh Moose checks closer than I assumed. Sorry about that I didn't test
that example before I'd written it.

> In my scenario, I have lots loads of little objects that
> I am pulling from a middleware layer and caching.
>
> The objects need to have access to a default API/Connector object
> (singleton) that can be used to find related objects.
>
> * I don't want this API object to appear in the data of the object
>  when you serialise it. *
>
> Normally you would do the following:
>
> package LilObject;
>
> has 'APIconnector' => (
>  isa=>'rw',
>  default => sub { APIconnector->new() }
>  handles => [qw/load reload save find_related find_parent .. /]
> );
>
> But here you get a $self->{APIconnector};
>
> If I serialise this with Data::Dumper or Storable, I get the whole
> of APIconnector too , which id like to avoid.
> Given that APIconnector is a singleton, the object doesn't need
> to maintain a reference to it.
> There are ocassions when I may need to manually override the
> default APIconnector object
> and then it is fine that a reference to it is held.
>
>
> So ideally...
>
>  my $ob =  LilObject->new();
>  $ob->save;   # triggers default APIconnectorSingleton
>
>  print Data::Dumper($ob2)
>  $VAR1 = bless( {
>               }, 'LilObject' );  # no {APIconnector} visible
>
>
>
>  my $ob2 = LilObject->new(APIconnector=> APIconnector->new( xyz=>1 );
>  $ob->save;   # delegates to provided APIconnector
>
>  print Data::Dumper($ob2)
>
>  $VAR1 = bless( {
>                  APIconnector => bless ( { xyz=>1 }, 'APIconnector )
>               }, 'LilObject' );
>
>
> In another scenario, id also like to dynamically choose the object
> that is delegated to. So the 'around' method would suit that.
>
> has 'foo' (is =>'rw', handles => [qw/x y z/] )
> around 'foo' => (
>  if (some clause) {
>     return $Foos::a
>  } else {
>     return $Foos::b
>  }
> }
>
> The problem seems to be that delegation errors if foo is not defined.
>
>
> For now im using:
>
> sub foo { MSDW::Oscar::Foo->new() };
>
> #set up some delegators
> foreach my $name (qw/Meth1 meth2 /) {
>  method $name => () => sub { shift->foo->$name(@_) };
> }
>
> But this wont allow me to override the value with a provided one
>
>
>
>
> Thanks, Tom


I think you'll have to explicitly write the checks you want with something like:

for my $name (qw(m1 m2 m3 ...)) {
__PACKAGE__->meta->add_method($name => sub {
    my ($self) = (shift);
    return Foo->instance->$name(@_) unless $self->_has_foo;
    return $self->foo->$name(@_);
});
}

which is a hybrid between your technique and mine.

Also you might look into MooseX::Storage for your serialization
because it does provide a "DoNotSerialize" metaclass trait for
attributes.

-Chris

Reply via email to