http://www.perlmonks.org?node_id=1036009


in reply to Re: a State machine with Roles - possible? (class or instance)
in thread a State machine with Roles - possible?

Seems Moose allows you to attach roles to instances, not just classes, with Moose::Util::apply_all_roles($applicant, @roles) by creating a new anonymous class with the object's previous class as superclass and with the roles applied, and then reblessing the object into that class. Not sure the other role implementations have easily accessible methods to do this.


UPDATE: using Moo::Role or Role::Tiny this seems to work bless $object, Moo::Role->create_class_with_roles(blessed $object, @roles); (replace Moo::Rule with Rule::Tiny for less Moo-ness, and import blessed from Scalar::Util)

Replies are listed 'Best First'.
Re^3: a State machine with Roles - possible? (class or instance)
by LanX (Saint) on May 30, 2013 at 10:32 UTC
    Well yesterday I meditated about ways to implement this in Perl...

    While I don't know the Moose guts, I remember that Moose was said to stay compatible to classical OO, mostly just adding syntactic sugar. ²

    So I suppose that the normal search-path isn't manipulated in Moose.

    This led me to 2 possibilities to implement this (rather JS-ish) requirement.

    > by creating a new anonymous class with the object's previous class as superclass and with the roles applied,

    Thats the first approach I thought about. But I'm worried about side effects. How transparent is this new class in the middle, does the instance still officially belong to the superclass?

    FWIW my approach to implement the OP's state machine would certainly be to openly create sub-classes for each state with a common superclass defining the interface.

    Like this state-changes could simply be realized with reblessings.

    Different roles could still be applied to these "state"-classes, can't see any need to avoid inheritance here.

    The other approach to have individual methods per instance could defining an attribute hash instance_methods which holds names => sub {} for each instance-method.

    However calling this indiviual methods would imply the need to inherit equally named interface methods in the class to delegate the call.¹

    Alternative an AUTOLOAD mechanism comes into mind, but these methods would have the lowest priority in the search path then.

    BUT I can't see any need to implement a state machine this way.

    IMHO the euphoria for mixins in Ruby was taken a bit to far.

    Cheers Rolf

    ( addicted to the Perl Programming Language)

    UPDATE

    ¹) such that  $obj->my_meth() does something like $self->{instance_methods}{my_meth}->(@_).

    ²) plz correct me if I'm wrong!

      Thanks for the discussion LanX :-) I think that you're looking for something too complicated, and that Roles just do the trick. (i might well be wrong). Here is the Perl code for getting the same behavior as the Ruby code from the slides (it could all be shorter and put in the same file, with MooseX::Declare - but some people might not have it installed):

      test.pl

      use feature ':5.16'; use strictures; use Door; use Moose::Util qw( apply_all_roles ); my $main_door = Door->new; # The Door is Closed $main_door->knock; # knock knock # Open it apply_all_roles($main_door, 'Opened'); $main_door->knock; # just come on in! # Close it apply_all_roles($main_door, 'Closed'); $main_door->knock; # knock knock # Bug ?? say 'Why does this not print ??' if $main_door->DOES('Closed');

      Door.pm

      package Door; use Moo; use feature ':5.16'; use Moose::Util qw( apply_all_roles ); sub BUILD { apply_all_roles($_[0], 'Closed'); # default State at construction } sub knock { say 'DEFECT: a door should never be nor opened nor closed'; # This method will be overriden by the Roles # (should never be printed) } 1;

      Closed.pm

      package Closed; use Moose::Role; use feature ':5.16'; sub knock { say 'knock knock'; } 1;

      Opened.pm

      package Opened; use Moose::Role; use feature ':5.16'; sub knock { say 'just come one in!'; } 1;

      Which does what is expected (output written as comments in test.pl).

      But it has several problems:

      • To check whether a Door is closed or opened, i need to check if the object DOES any of these two Roles. Unfortunately, DOES seems to not work, for a Role instanciated on an object. This might be a bug in Moose::Object::DOES. (see the last line of test.pl)
      • When a door is Closed, it is still Opened at the same time. I need to be able to unapply_role(). Which is what they do in the Ruby example with 'unmixin'. And it's my initial question in this thread: how to do it in Perl?

      If there were simple solutions to these two problems, then i think we could implement complex State machines with multidimensional states.

      There is here someone who asked pretty much the same question as me, with a pretty good example of multidimensional State machine: a warrior who can be turned into a Zombie, poisoned, made stronger, turned into a Chipmunk, or all of these at the same time. The answer to that person's question was an alternative solution with aspects. I would still prefer the solution with Roles, which are more flexible, and in my opinion clearer.

      Was that clear? Helpful? Did i miss your point LanX?

        then i think we could implement complex State machines with multidimensional states.

        Well, actually, just a small subset. Those whose response to an event may only depend on one dimension of the state. Let's call them linear multidimensional state-machines. Those are pretty uninteresting, and can be simulated easily by an aggregation of unidimensional state-machines.

        In practice, you will find that there are interactions between the state dimensions and so, in order to model the responses to the different events that may array you will have to take into consideration several dimensions. In other words, behavior can be modeled as a multidimensional matrix of responses.

        My experience is that when one gets to the point of needing a multi-dimensional non-linear state machines to solve some problem* it just means that he is doing something wrong and needs to get back and try to break the problem in simpler ones.

        *) unless there is some kind of code generator doing it!

        # Bug ?? say 'Why does this not print ??' if $main_door->DOES('Closed');

        This doesn't work because you're mixing Moo and Moose in the same package, not sure if that counts as a bug, but using Moose consistently (or Moo) makes DOES work properly.
        To unapply (which doesn't seem to be supported in Moose nor Moo at the moment), presuming you have access to the original roles applied:
        • find the superclass of the object (this will be the original class of the object without the roles applied)
        • subtract the roles to unapply from the list of roles applied
        • re-bless the object into its original class (in Moo, this is only necessary if the roles list is empty, as you use the class name in the next step)
        • if the roles list isn't empty, create a new role application with the object's original class and new list of roles,
          bless the object into this new class (in Moose, apply roles)
        I'm neither a Moose nor a Ruby expert, but I think with this Ruby code one can instantiate multiple objects each with individual states.

        But since Moose roles operate on classes you'll be restricted on a singleton class, i.e. just one object (resp. all objects with the same state.)

        But again theoretical thoughts, maybe better if I refrain from this subject.

        But thanks for bringing it up, I learned a lot.

        Cheers Rolf

        ( addicted to the Perl Programming Language)

        PS: I just remember that I once had a similar requirement which I solved with a state attribute.

        So calling $self->{state}{method}() solved it for me and I was able to have multidimensional states per object.

        Thats very similar to the already proposed proxy-methods.