Thursday, July 16, 2009

More Thoughts on Parameterized Roles

So my last post on parameterized roles has really got me thinking. One of the first use cases I ran into for parameterized roles was MooseX::Storage. Myself and Chris Prather wrote it on the train into NYC one day and since there was no such thing as MooseX::Role::Parameterized yet we hacked it with an exported Storage subroutine which composing in multiple roles based on the parameters that were passed to it. This is actually still how MooseX::Storage works because, well, it Just Works tm so there is no need to change it. But I decided as a thought experiment and a test of the Role Functor idea from my last post to see if I could re-write MooseX::Storage in terms of it. Much to my delight it not only worked, but came out very cleanly. 

Now, this is still written in MooseX::Declare inspired pseudo-code, so it is not yet a reality, but I am getting more and more convinced that this is something I really need to write. So anyway, here goes.

role COLLAPSER { requires 'pack', 'unpack' }
role FORMATTER { requires 'thaw', 'freeze' }
role IO        { requires 'load', 'store'  }

role DefaultCollapser with COLLAPSER {

    method pack {
        Collapser::Engine->new( object => $self )
                         ->collapse_object
    }

    method unpack ($class:, $data) {
        Collapser::Engine->new( class => $class )
                         ->expand_object( $data )
    }
}

role JSONFormatter [ 
        Collapser => (does => COLLAPSER) 
    ] with FORMATTER {

    method thaw ($class:, $json) {
        $class->unpack( JSON::Any->encode( $json ) )
    }

    method freeze {
        JSON::Any->decode( $self->pack )
    }
}

role SimpleFile [ 
        Formatter => (does => FORMATTER) 
    ] with IO {

    method load ($class:, $filename){
        my $fh   = IO::File->new( $filename, 'r' );
        my $data = do { local $/; <$fh>; };
        $class->thaw( $data );
    }

    method store ($filename) {
        my $fh = IO::File->new( $filename, 'w' );
        $fh->print( $self->freeze );
    }
}

I am obviously punting on a couple of details here to keep things simple for the example, but I think it gets the point across.  The nice part, in my opinion, is that the parameterization nicely captures the "levels" of serialization. For instance, here is what a class that does all the options would look like:

class Point 
 with SimpleFile( 
          Formatter => JSONFormatter( 
              Collapser => DefaultCollapser 
         ) 
    ) {
    has x => (is => rw, isa => Int, default => 0);
    has y => (is => rw, isa => Int, default => 0);

    method clear {
        $self->x(0);
        $self->y(0);
    }
}

And here is a class which does not do the load/store but just does the JSON freeze/thaw:

class Point
 with JSONFormatter( 
          Collapser => DefaultCollapser 
    ) {
    has x => (is => rw, isa => Int, default => 0);
    has y => (is => rw, isa => Int, default => 0);

    method clear {
        $self->x(0);
        $self->y(0);
    }
}

And here is a class which does only the simple pack/unpack:

class Point with DefaultCollapser {
    has x => (is => rw, isa => Int, default => 0);
    has y => (is => rw, isa => Int, default => 0);

    method clear {
        $self->x(0);
        $self->y(0);
    }
}

Overall I am quite happy with this, so now it is just a matter of finding the tuits to actually implement it.

No comments:

Post a Comment