Re: Role methods on roles, not classes

2009-10-12 Thread Chris Prather
On Mon, Oct 12, 2009 at 11:53 AM, Ovid  wrote:
> --- On Mon, 12/10/09, Jesse Luehrs  wrote:
>
>> From: Jesse Luehrs 
>
>> Yeah, something like this would work:
>>
>>   package Thing;
>>   use Moose;
>>   has robot_arm => (
>>       is      => 'ro',
>>       isa     => 'RobotArm',
>>       default => sub {
>>           my $self = shift;
>>           return RobotArm->new(to_draw => $self);
>>       },
>>       handles => { draw_with_arm => 'draw' },
>>   );
>
> So instead of this:
>
>  package Thing;
>  use Moose;
>  with (
>    DoesRobot => { excludes => 'draw', aliases => { draw => 'draw_with_arm' } 
> },
>    'DoesDrawable'
>  );
>
> You recommend this:
>
>  package Thing;
>  use Moose;
>  has robot_arm => (
>    is      => 'ro',
>    isa     => 'RobotArm',
>    default => sub { RobotArm->new({ to_draw => shift }) },
>    handles => { draw_with_arm => 'draw' },
>  );
>
> This is interesting in a couple of ways.  Aside from the fact that we have 
> more scaffolding code we have to write, we no longer get our composition-time 
> safety from 'requires' which roles provide.  With delegation, if $self 
> doesn't provide the methods you need, you get your failures at runtime 
> instead of composition time.  This is a problem when delegation requires 
> bi-directional in the sending and receiving objects.

class RobotArm {

has object => (
   isa => duck_type([qw(size, shape, color)]),
   init_arg => 'to_draw',
   required => 1,
}

}

This doesn't happen at composition time but does happen at object
creation time. The problem is this metaphor is getting abused, and the
reality is "it depends" on your specific circumstances. If you want a
program to be provably correct at compilation time, perhaps it's time
to look at a language that focuses on that, like OCaml?

> Mind you, I'm not saying you're wrong.  I think this technique is fine if you 
> already have a RobotArm class handling this particular case.  I'm just toying 
> more and more with the idea of assembling classes out of role-based 
> components (something I'm not pushing for production, of course).  Separation 
> of concerns is important, but I want to hear more concrete wins of delegation 
> over role composition :)

You've got a chicken egg problem then. If you only use this when a
RobotArm already exists, you never write a RobotArm class because one
doesn't exist ...

More concrete examples other than the fact that roles don't even solve
the problem that started this thread? Look at the implementation of
MooseX::Storage which is a *real world* use case for what your asking.
the Storage role has an Engine delegate, this Engine delegate handles
the heavy lifting of the business logic dealing with serialization.
KiokuDB goes further by abstracting the concept of serialization out
of the Object itself and into a process that happens upon an object.
As an experiment why not try re-implementing MooseX::Storage using
*only* Roles? I'm fairly certain either you'll prove your point, or
find out quickly what we're nattering on about.

-Chris


Re: Role methods on roles, not classes

2009-10-12 Thread Jesse Luehrs
On Mon, Oct 12, 2009 at 12:09:26PM -0400, Hans Dieter Pearcey wrote:
> The fact that a hammer's no good for washing clothes doesn't mean you can't
> still bash nails with it.
> 
>   package HasRobot;
>   use Moose::Role;
>   requires @some_methods; 
> 
>   has robot_arm => (
> is => 'ro',
> isa => 'RobotArm',
> lazy => 1,
> default => sub { RobotArm->new({ to_draw => shift }) },
> handles => { draw_with_arm => 'draw' },
>   );
> 
> (If you don't like hardcoding draw_with_arm, use MooseX::Role::Parameterized 
> to
> refactor it.)
> 
> Alternately, make a RobotArm::Drawable role that just requires @some_methods.

Alternatively again:

  package RobotArmLike;
  use Moose::Role;
  requires 'draw_with_arm';

  package RobotArm;
  use Moose;
  with 'RobotArmLike';

  sub draw_with_arm { ... }

  package Thing;
  use Moose;

  has robot_arm => (
  is => 'ro',
  does => 'RobotArmLike',
  handles => 'RobotArmLike',
  default => sub { RobotArm->new(to_draw => shift) },
  );

since delegation can delegate to the list of methods provided by a role.

-doy


Re: Role methods on roles, not classes

2009-10-12 Thread Hans Dieter Pearcey
Excerpts from Ovid's message of Mon Oct 12 11:53:23 -0400 2009:
> So instead of this:
> 
>   package Thing;
>   use Moose;
>   with (
> DoesRobot => { excludes => 'draw', aliases => { draw => 'draw_with_arm' } 
> },
> 'DoesDrawable'
>   );
> 
> You recommend this:
> 
>   package Thing;
>   use Moose;
>   has robot_arm => (
> is  => 'ro',
> isa => 'RobotArm',
> default => sub { RobotArm->new({ to_draw => shift }) },
> handles => { draw_with_arm => 'draw' },
>   );
> 
> This is interesting in a couple of ways.  Aside from the fact that we have 
> more scaffolding code we have to write, we no longer get our
> composition-time safety from 'requires' which roles provide.  With 
> delegation, if $self doesn't provide the methods you need, you get your
> failures at runtime instead of composition time.  This is a problem when 
> delegation requires bi-directional in the sending and receiving
> objects.

The fact that a hammer's no good for washing clothes doesn't mean you can't
still bash nails with it.

  package HasRobot;
  use Moose::Role;
  requires @some_methods; 

  has robot_arm => (
is => 'ro',
isa => 'RobotArm',
lazy => 1,
default => sub { RobotArm->new({ to_draw => shift }) },
handles => { draw_with_arm => 'draw' },
  );

(If you don't like hardcoding draw_with_arm, use MooseX::Role::Parameterized to
refactor it.)

Alternately, make a RobotArm::Drawable role that just requires @some_methods.

hdp.


Re: Role methods on roles, not classes

2009-10-12 Thread Ovid
--- On Mon, 12/10/09, Jesse Luehrs  wrote:

> From: Jesse Luehrs 

> Yeah, something like this would work:
> 
>   package Thing;
>   use Moose;
>   has robot_arm => (
>       is      => 'ro',
>       isa     => 'RobotArm',
>       default => sub {
>           my $self = shift;
>           return RobotArm->new(to_draw => $self);
>       },
>       handles => { draw_with_arm => 'draw' },
>   );

So instead of this:

  package Thing;
  use Moose;
  with (
DoesRobot => { excludes => 'draw', aliases => { draw => 'draw_with_arm' } },
'DoesDrawable'
  );

You recommend this:

  package Thing;
  use Moose;
  has robot_arm => (
is  => 'ro',
isa => 'RobotArm',
default => sub { RobotArm->new({ to_draw => shift }) },
handles => { draw_with_arm => 'draw' },
  );

This is interesting in a couple of ways.  Aside from the fact that we have more 
scaffolding code we have to write, we no longer get our composition-time safety 
from 'requires' which roles provide.  With delegation, if $self doesn't provide 
the methods you need, you get your failures at runtime instead of composition 
time.  This is a problem when delegation requires bi-directional in the sending 
and receiving objects.

Mind you, I'm not saying you're wrong.  I think this technique is fine if you 
already have a RobotArm class handling this particular case.  I'm just toying 
more and more with the idea of assembling classes out of role-based components 
(something I'm not pushing for production, of course).  Separation of concerns 
is important, but I want to hear more concrete wins of delegation over role 
composition :)

Cheers,
Ovid
--
Buy the book - http://www.oreilly.com/catalog/perlhks/
Tech blog- http://use.perl.org/~Ovid/journal/
Twitter  - http://twitter.com/OvidPerl
Official Perl 6 Wiki - http://www.perlfoundation.org/perl6



Re: Role methods on roles, not classes

2009-10-12 Thread Chris Prather
On Mon, Oct 12, 2009 at 5:54 AM, Ovid  wrote:
> - Original Message 
>
>> From: Jesse Luehrs 
>
>> >   package Thing;
>> >   use Moose;
>> >   with (
>> >     DoesRobot => { excludes => 'draw', aliases => { draw => 
>> > 'draw_with_arm' },
>> >     'DoesDrawable'
>> >   );
>>
>> Again, if the robot was a delegate rather than a role, you'd have
>> $thing->draw_with_arm delegated to $thing->robot_arm->draw, but
>> $thing->robot_arm->draw would still be available to be called
>> separately:
>>
>>   package Thing;
>>   use Moose;
>>   with 'DoesDrawable';
>>
>>   has robot_arm => (
>>       is      => 'ro',
>>       isa     => 'RobotArm',
>>       default => { RobotArm->new },
>>       handles => { draw_with_arm => 'draw' },
>>   );
>
> I've been trying to write up a response to this and I note that the 
> delegation example isn't equivalent.  Consider the following code:
>
>    #!/usr/bin/perl
>
>    {
>        package RobotArm;
>        use Moose;
>        use Data::Dumper;
>        sub draw { print STDERR Dumper( \...@_ ); }
>    }
>    {
>        package Thing;
>        use Moose;
>        has robot_arm => (
>            is      => 'ro',
>            isa     => 'RobotArm',
>            default => sub { RobotArm->new },
>            handles => { draw_with_arm => 'draw' },
>        );
>    }
>    my $thing = Thing->new;
>    $thing->draw_with_arm;
>
> That will print out:
>
>    $VAR1 = [
>          bless( {}, 'RobotArm' )
>    ];
>
> In short, the robot arm doesn't know what to draw because the original 
> invocant is not passed.  How is this handled in Moose?  I assume it's a 
> matter of somehow passing the invocant to the default?

Actually in your original email you never specified that was part of
the problem domain (you did however allude to it). The obvious
solution is:

sub draw_self_with_arm {
my $self = shift;
$self->draw_with_arm($self);
}

or to not set up the delegate method at all and simply do

sub draw_with_arm {
my $self = shift;
$self->robot_arm->draw($self);
}

Which is I *think* equivalent to what you were going to implement when you said:

>>> What's the easiest way to *not* worry about this method being renamed?  Is 
>>> it something like this awful pseudo-code?
>>>
>>> $thing->meta->does_role('DoesRobot')->get_method('draw')->($thing);
>>>
>>>Does that make sense?

A direct translation of this would be (using the delegation Jesse showed)

$thing->draw_with_arm($thing)

or alternatively

$thing->robot_arm->draw($thing)

-Chris

UPDATE: Jesse just replied with an example of passing the current
instance into the RobotArm class, I intentionally chose to ignore that
because it wasn't in your original examples and it seems a poor way to
create a RobotArm class, but in other cases it would be easiest and
probably the best solution.


Re: Role methods on roles, not classes

2009-10-12 Thread Jesse Luehrs
On Mon, Oct 12, 2009 at 02:54:05AM -0700, Ovid wrote:
> - Original Message 
> 
> > From: Jesse Luehrs 
> 
> > >   package Thing;
> > >   use Moose;
> > >   with (
> > > DoesRobot => { excludes => 'draw', aliases => { draw => 
> > > 'draw_with_arm' },
> > > 'DoesDrawable'
> > >   );
> >
> > Again, if the robot was a delegate rather than a role, you'd have
> > $thing->draw_with_arm delegated to $thing->robot_arm->draw, but
> > $thing->robot_arm->draw would still be available to be called
> > separately:
> > 
> >   package Thing;
> >   use Moose;
> >   with 'DoesDrawable';
> > 
> >   has robot_arm => (
> >   is  => 'ro',
> >   isa => 'RobotArm',
> >   default => { RobotArm->new },
> >   handles => { draw_with_arm => 'draw' },
> >   );
> 
> I've been trying to write up a response to this and I note that the 
> delegation example isn't equivalent.  Consider the following code:
> 
> #!/usr/bin/perl
> 
> {
> package RobotArm;
> use Moose;
> use Data::Dumper;
> sub draw { print STDERR Dumper( \...@_ ); }
> }
> {
> package Thing;
> use Moose;
> has robot_arm => (
> is  => 'ro',
> isa => 'RobotArm',
> default => sub { RobotArm->new },
> handles => { draw_with_arm => 'draw' },
> );
> }
> my $thing = Thing->new;
> $thing->draw_with_arm;
> 
> That will print out:
> 
> $VAR1 = [
>   bless( {}, 'RobotArm' )
> ];
> 
> In short, the robot arm doesn't know what to draw because the original 
> invocant is not passed.  How is this handled in Moose?  I assume it's a 
> matter of somehow passing the invocant to the default?

Yeah, something like this would work:

  package Thing;
  use Moose;
  has robot_arm => (
  is  => 'ro',
  isa => 'RobotArm',
  default => sub {
  my $self = shift;
  return RobotArm->new(to_draw => $self);
  },
  handles => { draw_with_arm => 'draw' },
  );

-doy


Re: Role methods on roles, not classes

2009-10-12 Thread Ovid
- Original Message 

> From: Jesse Luehrs 

> >   package Thing;
> >   use Moose;
> >   with (
> > DoesRobot => { excludes => 'draw', aliases => { draw => 'draw_with_arm' 
> > },
> > 'DoesDrawable'
> >   );
>
> Again, if the robot was a delegate rather than a role, you'd have
> $thing->draw_with_arm delegated to $thing->robot_arm->draw, but
> $thing->robot_arm->draw would still be available to be called
> separately:
> 
>   package Thing;
>   use Moose;
>   with 'DoesDrawable';
> 
>   has robot_arm => (
>   is  => 'ro',
>   isa => 'RobotArm',
>   default => { RobotArm->new },
>   handles => { draw_with_arm => 'draw' },
>   );

I've been trying to write up a response to this and I note that the delegation 
example isn't equivalent.  Consider the following code:

#!/usr/bin/perl

{
package RobotArm;
use Moose;
use Data::Dumper;
sub draw { print STDERR Dumper( \...@_ ); }
}
{
package Thing;
use Moose;
has robot_arm => (
is  => 'ro',
isa => 'RobotArm',
default => sub { RobotArm->new },
handles => { draw_with_arm => 'draw' },
);
}
my $thing = Thing->new;
$thing->draw_with_arm;

That will print out:

$VAR1 = [
  bless( {}, 'RobotArm' )
];

In short, the robot arm doesn't know what to draw because the original invocant 
is not passed.  How is this handled in Moose?  I assume it's a matter of 
somehow passing the invocant to the default?

 
Cheers,
Ovid
--
Buy the book - http://www.oreilly.com/catalog/perlhks/
Tech blog- http://use.perl.org/~Ovid/journal/
Twitter  - http://twitter.com/OvidPerl
Official Perl 6 Wiki - http://www.perlfoundation.org/perl6


Re: Role methods on roles, not classes

2009-10-08 Thread Jesse Luehrs
On Thu, Oct 08, 2009 at 10:06:06AM -0500, Jesse Luehrs wrote:
> Again, if the robot was a delegate rather than a role, you'd have
> $thing->draw_with_arm delegated to $thing->robot_arm->draw, but
> $thing->robot_arm->draw would still be available to be called
> separately:

Another thing about delegation that you might like is that delegation
will throw a conflict error for any method already existing in the
current class (not superclasses), so it gets rid of that "class silently
overrides role methods" issue you've been having(:

-doy


Re: Role methods on roles, not classes

2009-10-08 Thread Jesse Luehrs
On Thu, Oct 08, 2009 at 07:26:40AM -0700, Ovid wrote:
> Hi all,
> 
> This problem is annoying and I'm not sure of the best approach.
> 
> Let's say I have a list of roles and an object and I want to serialize the 
> object as XML.  Imagine the following (this is my actual use case):
> 
>   sub to_xml {
>   my $self = shift;
>   my $xml = ''; # string XML sucks. Just an example :)
>   foreach my $role ($self->meta->roles) { # or whatever it's called
> next unless $role->meta->does('DoesXML');
> $xml .= $role->as_xml($self); # method available on the role itself
>   }
>   return $xml;
>   }

I think a lot of this issue comes from issues with the actual design
here. When you compose a role into a class, it's an actual part of the
class, it doesn't really make sense to need to serialize it separately.
If it does, then you really don't want to be using roles, you want to be
using an attribute on your class holding an object of another class, and
delegating methods to that attribute. That way, the delegated-to class
can know how to serialize itself, and this issue just goes away.

One thing I have noticed in general from reading your blog posts and
such (and I'm not trying to be snarky or insulting or anything) is that
you seem to be caught in the mindset of "not inheritance, therefore
roles", when there are more options available than just those two.
nothingmuch makes this case better than I do in his latest blog post(:

> In other words, tying methods and roles specifically to roles and their state 
> might be useful.  Here's a better(?) example, also pseudo-code:
> 
>   package Thing;
>   use Moose;
>   with qw(
> DoesRobot
> DoesDrawable
>   );
>  
> Now imagine that both the robot and drawable roles provide a draw() method, 
> but the robot on tries to control a robotic arm with a pencil.  Canonically, 
> you do something like this:
> 
>   package Thing;
>   use Moose;
>   with (
> DoesRobot => { excludes => 'draw', aliases => { draw => 'draw_with_arm' },
> 'DoesDrawable'
>   );
> 
> Now if another class tries to instantiate Thing->new, it doesn't really know 
> what the draw() method has been renamed to for the Robot role.  You can get 
> to it through reflection (I think), but that's annoying.  What's the easiest 
> way to *not* worry about this method being renamed?  Is it something like 
> this awful pseudo-code?
> 
>   $thing->meta->does_role('DoesRobot')->get_method('draw')->($thing);
> 
> Does that make sense?

Again, if the robot was a delegate rather than a role, you'd have
$thing->draw_with_arm delegated to $thing->robot_arm->draw, but
$thing->robot_arm->draw would still be available to be called
separately:

  package Thing;
  use Moose;
  with 'DoesDrawable';

  has robot_arm => (
  is  => 'ro',
  isa => 'RobotArm',
  default => { RobotArm->new },
  handles => { draw_with_arm => 'draw' },
  );

Ending up in this situation in general is something of a code smell...
if you aren't using excludes/alias to just provide an implementation of
the method that makes more sense for the *class*, you probably want to
think about if there is another way to be doing what you're doing.
Thinking of roles as separate objects really just confuses things. (Not
that I'm saying that's how you're thinking about things, but that's how
it comes across from your code.)

Anyway, sorry I wasn't more helpful with your actual problems, but
hopefully there's at least some stuff to think about here(:

-doy


Role methods on roles, not classes

2009-10-08 Thread Ovid
Hi all,

This problem is annoying and I'm not sure of the best approach.

Let's say I have a list of roles and an object and I want to serialize the 
object as XML.  Imagine the following (this is my actual use case):

  sub to_xml {
  my $self = shift;
  my $xml = ''; # string XML sucks. Just an example :)
  foreach my $role ($self->meta->roles) { # or whatever it's called
next unless $role->meta->does('DoesXML');
$xml .= $role->as_xml($self); # method available on the role itself
  }
  return $xml;
  }

In other words, tying methods and roles specifically to roles and their state 
might be useful.  Here's a better(?) example, also pseudo-code:

  package Thing;
  use Moose;
  with qw(
DoesRobot
DoesDrawable
  );
 
Now imagine that both the robot and drawable roles provide a draw() method, but 
the robot on tries to control a robotic arm with a pencil.  Canonically, you do 
something like this:

  package Thing;
  use Moose;
  with (
DoesRobot => { excludes => 'draw', aliases => { draw => 'draw_with_arm' },
'DoesDrawable'
  );

Now if another class tries to instantiate Thing->new, it doesn't really know 
what the draw() method has been renamed to for the Robot role.  You can get to 
it through reflection (I think), but that's annoying.  What's the easiest way 
to *not* worry about this method being renamed?  Is it something like this 
awful pseudo-code?

  $thing->meta->does_role('DoesRobot')->get_method('draw')->($thing);

Does that make sense?

Cheers,
Ovid
--
Buy the book - http://www.oreilly.com/catalog/perlhks/
Tech blog- http://use.perl.org/~Ovid/journal/
Twitter  - http://twitter.com/OvidPerl
Official Perl 6 Wiki - http://www.perlfoundation.org/perl6