Some of you might recall chromatic’s review of Class::Trait, a module which implements traits (PDF) in Perl. He gave a couple of good examples of how one can use traits and I figured a follow-up on one of my many “real world” uses of traits and how it has simplified my code.

I was recently working on a command line program and came upon the following line of code:

@servers = map { $_->{selected} ? $_->{obj} : () } @servers;

That is just ugly for a variety of reasons, but here’s why it was created. We were presenting the users with lists and they needed to select one or more items from those lists. When the program was written, only one type of item (a server object) was created for this list. To track whether a server was selected from the list, the following data structure was used.

my $list_item = { obj => $server, selected => 0, };

And that leads to to the above rather ugly map. In fact, the map statement could probably be written more clearly as follows:

@servers = map { $_->{obj} } grep { $_->{selected} } @servers;

That’s easier to read, but it’s still pretty ugly. Also, since we’re using hash keys, they’re really easy to mistype. And on top of that, I now need to reuse this construct for objects of different types so I was looking at potentially having to use that data structure in more than one spot. Now when writing code, having to essentially attach extra state data to objects is quite common. I’ve worked with plenty of classes which have notes() methods or something similar which allow you to shove anything in there you like. For classes which don’t provide this, we do all sorts of tricks like, in this case, wrapping them in data structures which can contain the extra data or separating the objects into different data structures depending on what extra data you want to capture (@selected and @unselected arrays come to mind). Or maybe you pass the objects one at a time to target methods which take extra arguments denoting the metadata. Who knows? Personally, I think traits are a better way to handle this.

All of these techniques have pros and cons, but what I really want is for the object to have that data available, not for me to jump through hoops to preserve that data. I can’t simply build all possible metadata types into all objects which might need them because that’s bloating my code for no reason. Instead, I want a trait which I can apply to a class at runtime and provides just what I need for my code. My first refactoring was a bit of a kludge, but what I have now looks a bit like this:

1 package Trait::Selected; 2 3 use strict; 4 use warnings; 5 6 use Class::Trait 'base'; 7 8 our @REQUIRES = qw(id); 9 10 my $get_id = sub { 11 my $self = shift; 12 return sprintf "%s %d" => ref $self, $self->id; 13 }; 14 15 my %is_selected; 16 17 sub is_selected { 18 my $self = shift; 19 my $id = $self->$get_id; 20 $is_selected{$id} ||= 0; 21 return $is_selected{$id} unless @_; 22 $is_selected{$id} = shift; 23 return $self; 24 } 25 26 1;

Line 6 says “this is a trait”. Line 8 says “any object which uses this trait must supply an id() method”. Line 10 is how you supply truly private methods or functions with traits and line 17 is the actual method that each class will now have.

In my actual code, it’s completely wrong for me to add an is_selected() method to a bunch of classes just because I need them for this one script, so I’m going to apply the trait at runtime with this:

Class::Trait->apply( $some_class, 'Trait::Selected' );

Of course, if you don’t want this to apply to all instances of that class, you can apply it just to a single instance by replacing $some_class with an instance of the object in question. (Prototype-based programming, anyone?)

Now that I have that, I can use is_selected() as a getter/setter (accessor/mutator, if you prefer) for this boolean property. So if a particular object from a list I present to the user is selected, I can just do this:

# This used to be $item->{selected} = 1; and is really easy to mistype $object->is_selected(1); # Mistyping the method becomes a runtime fai +lure

And this:

@servers = map { $_->{obj} } grep { $_->{selected} } @servers;

Becomes this:

@servers = grep { $_->is_selected } @servers

That’s much easier to read.

While this made much of my code easier to read and work with, it does show an ugly problem with trying to add runtime traits to languages not designed to support them. What happens when the object goes out of scope? The trait still retains that object’s selected value and it’s tricky to remove. Fortunately for my code, this turns out to not be an issue, but using runtime traits to maintain state is problematic. While Perl 6 doesn’t have this problem, for the time being, I need to solve it for Class::Trait. I have a reasonably workable approach, but it will take some time to implement. Once again, I find myself pining away for Perl 6 for the things that it makes easy.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Maintaining State with Runtime Traits (where)
by tye (Sage) on Oct 10, 2006 at 21:41 UTC
    We were presenting the users with lists and they needed to select one or more items from those lists.

    It seems to me that the appropriate place for my %is_selected; is inside the code that presents the list to the user and lets them select one or more items. And that code should know how to return the list of selected items (that code might be encapsulated in a class, depending).

    The problem with free'ing the %is_selected is just one symptom of a poor solution that doesn't scale. Consider if you need to select more than one set of servers. You only have one is_selected() method per object. But if you present the user with the opportunity to select a subset twice, then by putting the information about what they selected in a more appropriate place, there is no problem if you need multiple subsets selected (and this information is deleted when you are done with it).

    - tye        

      The problem with free'ing the %is_selected is just one symptom of a poor solution that doesn't scale.

      The problem with freeing that is a problem with adding traits to a language which doesn't natively support them. It has nothing to do with whether or not the concept is appropriate.

      As for the rest of what you typed, it might be a great way of looking at the problem but it's completely irrelevant to my specific coding problem. With runtime traits, my code is cleaner and easier to read. Solutions should be created to match actual problems faced, not "what if" scenarios -- unless those scenarios have a realistic chance of occurring. For the purposes of the code I was working on, it's been used for years and is quite stable. Your scenario is highly unlikely to apply.

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Maintaining State with Runtime Traits
by ysth (Canon) on Oct 11, 2006 at 09:21 UTC
    @servers = map { $_->{selected} ? $_->{obj} : () } @servers;
    That is just ugly for a variety of reasons, ... In fact, the map statement could probably be written more clearly as follows:
    @servers = map { $_->{obj} } grep { $_->{selected} } @servers;
    That’s easier to read, but it’s still pretty ugly.
    I don't find the latter ugly at all, and have no problem with the former either (in fact, I prefer it, as quicker to read shorthand for the latter).
Re: Maintaining State with Runtime Traits
by jdhedden (Deacon) on Oct 11, 2006 at 19:39 UTC
    Interesting. Your Trait::Selected class looks very much like an inside-out class. In fact, using Object::InsideOut it could be implemented, very simply, as follows:
    package Trait::Selected; use Object::InsideOut; my %is_selected :Field :Acc(is_selected);
    (Of course, looking at Class::Trait, I see that it offers a lot of functionality beyond that covered by my simple example above.)
    Class::Trait->apply( $some_class, 'Trait::Selected' );
    Ah, sweet synergy! This prompted me to add a runtime inheritance mechanism to Object::InsideOut:
    My::Class->add_class('Trait::Selected');
    What happens when the object goes out of scope? The trait still retains that object’s selected value and it’s tricky to remove.
    If done using Object::InsideOut, this is no issue at all: The trait will be automatically cleaned up when an object is destroyed.

    I just uploaded Object::InsideOut v2.08 (which contains the runtime inheritance feature) to CPAN. Thanks for the idea.


    Remember: There's always one more bug.

      Bink! That piques my interest. Can't wait to see it out there and I'll start playing with it. Maybe that's enough to get me over the learning curve hurdle. Thanks!

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Maintaining State with Runtime Traits
by diotalevi (Canon) on Oct 10, 2006 at 21:35 UTC

    With all this applying of runtime traits are you noticing that your ISA cache is nearly always invalid or what? When you apply a trait to an object does it make a class or what?

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

      Don't know why my reply didn't show up. I'll try again.

      The ISA cache isn't invalidated (that I'm aware of) because when applying the trait to a class, ISA isn't affected. The methods get compiled directly into the class. As for applying a trait to an instance, I follow the Perl 6 route and make a new anonymous class. That alters ISA and if I layer on several runtime traits it might be a problem, but for the most part, it's not caused me any difficulty.

      Cheers,
      Ovid

      New address of my CGI Course.

        Compiling a new method also clears the cache but that doesn't really matter since I expect there's a limited number of classes that this is happening to and it's not a "runtime" thing. Its when you go clearing your cache at runtime that you get perl's OO to start losing that 30-40% performance vs functions that we used to hear so much about. I've understood that it's all the having to do ISA method lookups that made perl OO slow and applying this stuff to objects during rumtime is buying that problem back again.

        But then elegant solutions are also very nice for a programmer and that might be more expensive yet than the CPU time.

        ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: Maintaining State with Runtime Traits
by xdg (Monsignor) on Oct 11, 2006 at 21:39 UTC

    It seems like this is a lot of effort to modify the objects instead of fixing the initial data structure used to track selections. For example, why not just add selected items to a Set::Object? (Or, for lower tech, just an array.)

    use Set::Object; my $selected = Set::Object->new; # create set $selected->insert( @some_objects ); # add objects @servers = $@selected; # get them back
    What happens when the object goes out of scope?

    You're using something like the inside-out technique with all the usual problems cleaning up afterwards. You could wrap the primary class' DESTROY method (or add one) to clean up the trait and then continue with destruction.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Maintaining State with Runtime Traits
by roman (Monk) on Oct 13, 2006 at 08:45 UTC

    When it comes to maintaining state I don't see much difference between runtime trait and statically applied trait. As far as I remember the original paper the traits are stateless by nature. So IMHO what you try to do is breaking the traits :-)

    But your need is also very natural. I often use Class::Trait and many of my traits would like to have their state variables (usually class or instance accessors).

    I solve their wish by a subroutine in the trait (currently named build_class) which I call immediately after the trait is applied. The build_class then modifies the class according to trait's needs.

    Class::Trait->apply( $some_class, $some_trait ); my $build_class_sub = $some_trait->can('build_class'); $build_class_sub->($some_class);

    Inside the trait I have, for example:

    sub build_class { my ($class) = @_; $class->mk_accessors( qw(smb_hostname) ); $class->mk_accessors( qw(smb_username) ); ... }

    I guess that with your inside out approach you would probably need something like:

    sub build_class { my ($class) = @_; my $destructor = sub { my ($this) = @_; delete $is_selected{ get_obj_id($this) }; }; $class->register_destructor($destructor); }

    I know that this approach is not very traity, but my primary goal is to find a consistent and clear way to build my classes not to use pure traits.

    There is also somehow complementary issue I need to solve with traits: fixing the name of applying class in the trait. In other words I need my __PACKAGE__ in the trait.

    Imagine few logging functions - perfect task for traits. One of them is the logging path for the module. Implemented in class it would look like:

    sub logpath { my ($this) = @_; return File::Spec->rel2abs( join( '-', split /::/, __PACKAGE__ ) . + '.log', $this->log_root ); }

    All subclasses log into the same file as the class which defined the method. My Durian::Call::Statement logs into $ROOT/Durian-Call-Statement.log as does its Durian::Call::Statement::Incoming subclass.

    But how to accomplish this task with trait? The only way I found is to modify the trait symbol table with respect to the class applying the trait immediately before the application. In other words apply a slightly modified trait to every class.

    To call preapply subroutine right before the trait methods are collected.

    sub preapply { my ($class) = @_; *logpath = sub { my ($this) = @_; return File::Spec->rel2abs( join( '-', split /::/, $class ) . +'.log', $this->log_root ); }; }

    With respect to preapply the better name for build_class would be postapply. It sounds good, doesn't it?

    Unless someone explains to me that it is a very bad idea I'll try to make my own preapply/postapply Class::Trait subclass.