Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How do I do parametered roles?

4 views
Skip to first unread message

Austin Hastings

unread,
Apr 30, 2004, 5:58:22 PM4/30/04
to Perl6 Language
Suppose that I have, for example:

class Session {
has @:messages;

method clear_messages() {...}
method add_message($msg) {...}
method have_messages() {...}
method get_messages() returns Array {... font color="black" ...}
}

And suppose I add to it:

class Session {
has @:messages;

method clear_messages() {...}
method add_message($msg) {...}
method have_messages() {...}
method get_messages() returns Array {... font color="black" ...}

has @:errors;

method clear_errors() {...}
method add_error($msg) {...}
method have_errors() {...}
method get_errors() returns Array {... font color="red" ...}
}

So I realize that not only is the whole list-of-messages thing a packageable
behavior (read: Role or Class) but that this particular behavior recurs
within my class.

One solution is to compose member objects that implement this behavior:

if ($my_object.messages.have_messages) {...}

But that sucks.

What I want to do is to say:

1- There's a "list of generic messages" role.
2- The ListOfMessages defines an attribute.
3- The ListOfMessages needs some sort of customizable behavior.

And then implement two different parameterized invocations of the same role
(MetaRole?)

class Session
{
does ListOfMessages
(
attribute_base => 'messages',
method_base => 'messages',
display_color => 'black'
);

does ListOfMessages
(
attribute_base => 'errors',
method_base => 'errors',
display_color => 'red'
);

...
}

(Classes are described as "parameterless subroutines". I'd like a
parameterful one.)

Can I just do:

role ListOfMessages($attribute_base, $method_base, $display_color) {...}

or what?

=Austin

Austin Hastings

unread,
Apr 30, 2004, 6:09:43 PM4/30/04
to Perl6 Language

> Can I just do:
>
> role ListOfMessages($attribute_base, $method_base, $display_color) {...}
>
> or what?

PS:

Sorry: I know that for types and what-not I could do

role ListOfMessages[args]

but I'm wondering about parameters in names.

That is,

has @:messages;

versus

has @:errors;

I don't think scads of little C<eval> statements is the answer, but I don't
know if

has @:${attribute_base};

works, or if there's some sort of higher-level method:

$this.meta.add_attribute(name => $attribute_base, type => '@', access =>
':')

Hopefully this is clearer.

=Austin

Aaron Sherman

unread,
Apr 30, 2004, 8:25:33 PM4/30/04
to Austin Hastings, Perl6 Language List
On Fri, 2004-04-30 at 17:58, Austin Hastings wrote:

> class Session
> {
> does ListOfMessages
> (
> attribute_base => 'messages',
> method_base => 'messages',
> display_color => 'black'
> );
>
> does ListOfMessages
> (
> attribute_base => 'errors',
> method_base => 'errors',
> display_color => 'red'
> );
>
> ...
> }

> Can I just do:


>
> role ListOfMessages($attribute_base, $method_base, $display_color) {...}

I think you want a combination of re-dispatch and delegation:

class Session {
role EventHandler {
# The one and only place you define the behavior
method clear() {...}
method add() {...}
method have() {...}
method get() {...}
}
class ErrorHanlder {
does EventHandler;
# Probably should do some funky re-dispatch thing
method clear_errors() { .clear() }
method add_errors() { .add() }
method have_errors() { .have() }
method get_errors() { .get() }
}
class MessageHanlder {
does EventHandler;
method clear_messages() { .clear() }
method add_messages() { .add() }
method have_messages() { .have() }
method get_messages() { .get() }
}
has ErrorHanlder $:errors handles <<clear_errors add_errors have_errors get_errors>>;
has MessageHandler $:messages handles <<clear_messages add_messages have_messages get_messages>>;
}

That should do what you want.

--
Aaron Sherman <a...@ajs.com>
Senior Systems Engineer and Toolsmith
"It's the sound of a satellite saying, 'get me down!'" -Shriekback


Austin Hastings

unread,
Apr 30, 2004, 11:12:42 PM4/30/04
to Perl6 Language List
(Note more precise topic.)

Well, not really:

1- It required a lot of text for little gain.
2- Most of the required text was redundant.

What I want is probably something like this:

role ListOfMessages[$method_base]
{
is Array;
handles (s/_$method_base/_messages/);

method clear_messages() { .clear(); }
method add_message($msg) { .push($msg); }

method have_messages() { return .#; }
# Isn't there a .# property for arrays?

method get_messages() { return $_; }
}

which is close to the same size as the original implementation, but still
doesn't speak to collision of the two lists: messages and arrays. I can move
the data declaration into the class, but this loses encapsulation:

class Session
{
has @:messages

does ListOfMessages«messages»;

has @:errors
does ListOfMessages«errors»;
}

It would be even better, of course, if the role could introspect the
variable name (assuming there is one, which isn't always true) and default
to using that for its method_base.

=Austin

Luke Palmer

unread,
May 1, 2004, 12:24:18 AM5/1/04
to Austin Hastings, Perl6 Language
Austin Hastings writes:
> Suppose that I have, for example:
>
> class Session {
> has @:messages;
>
> method clear_messages() {...}
> method add_message($msg) {...}
> method have_messages() {...}
> method get_messages() returns Array {... font color="black" ...}
> }
>
> And suppose I add to it:
>
> class Session {
> has @:messages;
>
> method clear_messages() {...}
> method add_message($msg) {...}
> method have_messages() {...}
> method get_messages() returns Array {... font color="black" ...}
>
> has @:errors;
>
> method clear_errors() {...}
> method add_error($msg) {...}
> method have_errors() {...}
> method get_errors() returns Array {... font color="red" ...}
> }
>
> So I realize that not only is the whole list-of-messages thing a packageable
> behavior (read: Role or Class) but that this particular behavior recurs
> within my class.

So what you want is a role whose names are private to the role, not the
aggreagating class. Well, I believe private (i.e. $:foo) variables do
that anyway, so you can just abstract your messages out into a role and
it will work fine. But if you read on, I'll offer some unsolicited
design advice.

> One solution is to compose member objects that implement this behavior:
>
> if ($my_object.messages.have_messages) {...}
>
> But that sucks.

Why? From a design perspective, that's the correct way to do it. What
if you have a routine that operates on message lists -- pruning them or
somesuch. If you do it by renaming the methods, you break any kind of
future-proof code reuse that abstracting gets you in the first place.
But I'll agree that:

if $my_object.messages.have_messages {...}

Kinda sucks. But you'll find that things that suck in this particular
way can be solved by tersifying your method names.

class MessageList {
method ready() {...}
method add($msg) {...}
method clear() {...}
method get() {...}
}

There's no reason to repeat _message on the end of each method when
you're already in a class about messages. And then you have:

if $my_object.messages.ready {
say for $object.messages.get;
}

Which is quite more tolerable, IMO.

But this isn't an OO design list, it's a language design list.

If it turns out that role private variables leak, I might suggest making
a MessageList class as above, then making a role that knows how to turn
a MessageList into a set of methods. Then you'd pass the role the
MessageList object which you instantiate yourself, and it would generate
the methods for you. That's kind of a kludge, but you're using roles
for something they're not really intended for, so that's what you get.
Keep in mind that there are plenty of other ways to do what you want;
this is Perl, after all.

Luke

Aaron Sherman

unread,
May 1, 2004, 12:26:22 AM5/1/04
to Austin Hastings, Perl6 Language List
On Fri, 2004-04-30 at 23:12, Austin Hastings wrote:

> > From: Aaron Sherman [mailto:a...@ajs.com]

> > I think you want a combination of re-dispatch and delegation:

[...example code...]


> > That should do what you want.
>
> Well, not really:
>
> 1- It required a lot of text for little gain.

Oh, I would never DO that, I was just complying with what you asked for.
I would have just called the methods on the encapsulated objects, which
you did not like.

> 2- Most of the required text was redundant.

Not really. I defined each of the methods you wanted once in the role
and then re-dispatched them manually in the message and error classes.
You should be able to override dispatch in those classes and remove the
extra definition of each method. I just didn't have a handle on the
re-dispatch part.

But, you didn't say you wanted golf, you said you wanted a solution that
avoided "$obj.message.clear", which is what I gave you. You do not need
parameterized roles of any sort for the solution.

signature.asc

Austin Hastings

unread,
May 1, 2004, 2:31:10 AM5/1/04
to Perl6 Language
> From: Luke Palmer [mailto:lu...@luqui.org]

Hmm. No. I want a role whose methods are available in the composing class, as shown. Variables (members) don't have roles: Roles (may) have variables.

Saying "class Session does MessageList" means that MessageList may impose member variables on Session, and will provide methods to Session. Roles are composition, not aggregation.

> > One solution is to compose member objects that implement this behavior:
> >
> > if ($my_object.messages.have_messages) {...}
> >
> > But that sucks.
>
> Why? From a design perspective, that's the correct way to do it. What
> if you have a routine that operates on message lists -- pruning them or
> somesuch. If you do it by renaming the methods, you break any kind of
> future-proof code reuse that abstracting gets you in the first place.

You've got it backwards: saying $my_object.messages.have_messages is exposing the fact that the have_messages functionality is implemented via a member data item, instead of querying the $my_object directly.

> But I'll agree that:
>
> if $my_object.messages.have_messages {...}
>
> Kinda sucks. But you'll find that things that suck in this particular
> way can be solved by tersifying your method names.
>
> class MessageList {
> method ready() {...}
> method add($msg) {...}
> method clear() {...}
> method get() {...}
> }
>
> There's no reason to repeat _message on the end of each method when
> you're already in a class about messages. And then you have:
>
> if $my_object.messages.ready {
> say for $object.messages.get;
> }
>
> Which is quite more tolerable, IMO.
>
> But this isn't an OO design list, it's a language design list.

And "how to make the names more readable" isn't "how to parameterize member/method names".

>
> If it turns out that role private variables leak, I might suggest making
> a MessageList class as above, then making a role that knows how to turn
> a MessageList into a set of methods. Then you'd pass the role the
> MessageList object which you instantiate yourself, and it would generate
> the methods for you. That's kind of a kludge, but you're using roles
> for something they're not really intended for, so that's what you get.
> Keep in mind that there are plenty of other ways to do what you want;
> this is Perl, after all.

Hmm. Why is this not what roles are intended for? I have identified a packageable, reusable behavior that I want to insert into a class using some mechanism other than inheritance. I could have sworn that was the whole point of roles.

In this particular case, however, I've gone one step beyond and identified a "generic" behavior, that I'd like to implement multiple times with slightly different names.

=Austin

Austin Hastings

unread,
May 1, 2004, 2:39:51 AM5/1/04
to Perl6 Language List
> From: Aaron Sherman [mailto:a...@ajs.com]
>
> On Fri, 2004-04-30 at 23:12, Austin Hastings wrote:
>
> > > From: Aaron Sherman [mailto:a...@ajs.com]
>
> > > I think you want a combination of re-dispatch and delegation:
> [...example code...]
> > > That should do what you want.
> >
> > Well, not really:
> >
> > 1- It required a lot of text for little gain.
>
> Oh, I would never DO that, I was just complying with what you asked for.
> I would have just called the methods on the encapsulated objects, which
> you did not like.
>
> > 2- Most of the required text was redundant.
>
> Not really. I defined each of the methods you wanted once in the role
> and then re-dispatched them manually in the message and error classes.
> You should be able to override dispatch in those classes and remove the
> extra definition of each method. I just didn't have a handle on the
> re-dispatch part.

Nor did I, until I started looking at your example (for which, thanks). My
point is that I do want a little bit of golf. I am a bear of very little
brain: if I can think of the question, the answer should be either easy or
impossible. If it's neither, then there's a flaw in my question, or in the
system. (Mostly it's my questions, but I do get lucky once in a while. ;-)

> But, you didn't say you wanted golf, you said you wanted a solution that
> avoided "$obj.message.clear", which is what I gave you. You do not need
> parameterized roles of any sort for the solution.

The solution isn't the interesting part: the problem is. The point of the
question was to come up with an example of templating/parameterized classes,
trying to use roles instead.

(The correct answer to the specific question is "<?php ...?>" :-)

=Austin

Luke Palmer

unread,
May 1, 2004, 3:31:46 AM5/1/04
to Austin Hastings, Perl6 Language
Austin Hastings writes:
> > So what you want is a role whose names are private to the role, not the
> > aggreagating class. Well, I believe private (i.e. $:foo) variables do
> > that anyway, so you can just abstract your messages out into a role and
> > it will work fine. But if you read on, I'll offer some unsolicited
> > design advice.
>
> Hmm. No. I want a role whose methods are available in the composing
> class, as shown. Variables (members) don't have roles: Roles (may)
> have variables.

Right, and those variables in turn become part of the *composing class.
Unless they're private.

> Saying "class Session does MessageList" means that MessageList may
> impose member variables on Session, and will provide methods to
> Session. Roles are composition, not aggregation.

Whoops, got my vocabulary backwards. Trust me, we're on the same page
here.

> > > One solution is to compose member objects that implement this behavior:
> > >
> > > if ($my_object.messages.have_messages) {...}
> > >
> > > But that sucks.
> >
> > Why? From a design perspective, that's the correct way to do it. What
> > if you have a routine that operates on message lists -- pruning them or
> > somesuch. If you do it by renaming the methods, you break any kind of
> > future-proof code reuse that abstracting gets you in the first place.
>
> You've got it backwards: saying $my_object.messages.have_messages is
> exposing the fact that the have_messages functionality is implemented
> via a member data item, instead of querying the $my_object directly.

This was in my "unsolicited design advice" section, where I was
proclaiming that it should be.

> > But this isn't an OO design list, it's a language design list.
>
> And "how to make the names more readable" isn't "how to parameterize
> member/method names".

Okay, so you want to use a role to integrate a behavior, but with PHPly
slightly different method names. You've given your brain time, and
after a few beers, it still decides it wants to do this. Well, you'll
be spending a lot of time hacking around, while someone else with an
equally insane idea has come up with a simple solution without using
roles:

class MessageList {
method have_messages() {...}
method get_messages() {...}
method add_message($msg) {...}
method clear_messages() {...}
}

class Session {
has $:messages handles /_messages?$/;
has $:errors handles (s/_error(s?)$/_message$1/);
}

> >
> > If it turns out that role private variables leak, I might suggest making
> > a MessageList class as above, then making a role that knows how to turn
> > a MessageList into a set of methods. Then you'd pass the role the
> > MessageList object which you instantiate yourself, and it would generate
> > the methods for you. That's kind of a kludge, but you're using roles
> > for something they're not really intended for, so that's what you get.
> > Keep in mind that there are plenty of other ways to do what you want;
> > this is Perl, after all.
>
> Hmm. Why is this not what roles are intended for? I have identified a
> packageable, reusable behavior that I want to insert into a class
> using some mechanism other than inheritance. I could have sworn that
> was the whole point of roles.
>
> In this particular case, however, I've gone one step beyond and
> identified a "generic" behavior, that I'd like to implement multiple
> times with slightly different names.

And it's that last part that roles weren't intended for. A role is like
a Java interface[1] in that it provides a set of names that other users
can count on being there. If you ask for PHPly, slightly modified names
then there's no set of names that the role provides. Now it's just
providing implementations, and you're providing the names. That sounds
much more like delegation to me. Or insanity.

Luke

[1] Except, because it can provide as well as require methods, it turns
out to be useful.

Aaron Sherman

unread,
May 1, 2004, 10:08:12 AM5/1/04
to Austin Hastings, Perl6 Language List
On Sat, 2004-05-01 at 02:39, Austin Hastings wrote:

> (The correct answer to the specific question is "<?php ...?>" :-)

You spelled that wrong. That's:

use Grammar::PHP;

;-)

signature.asc
0 new messages