Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

curried-up moose

by rodd (Beadle)
on Jan 17, 2009 at 21:37 UTC ( #737084=perlquestion: print w/replies, xml ) Need Help??

rodd has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks,
Desperatly need some Moose enlightment here.

I'm trying to find a away to create a curry style class called BodyParts using Moose. I want to use my class like this:

package main; use BodyParts; my $body = BodyParts->new; $body = $body->legs( size=>42 )->eyes( color => blue); print $body->eyes->color; ## blue

Basically my problem comes down to:

  • I don't want to write a lot of subclasses, just a single moosed up package in BodyParts.pm.
  • Every accessor should return $self
  • But I don't want to write around directives for every accessor I create just to return $self.

    Is there a MooseX extension that takes care of that? What's the true path to concise code here?
    Thanks for your enlightment,
    -rodrigo

  • Replies are listed 'Best First'.
    Re: curried-up moose
    by stvn (Monsignor) on Jan 18, 2009 at 01:43 UTC
      I don't want to write a lot of subclasses, just a single moosed up package in BodyParts.pm.

      Subclasses is the wrong word, you mean "inner" classes perhaps, which you could do within BodyParts.pm (you can have multiple classes per-file).

      Also, curry is the wrong word as well, you looking for classes that "autovivify". That is not something Moose supports as Moose is class based OOP and what your talking about here is more akin to prototype based OOP (like Javascript or Self).

      But I don't want to write around directives for every accessor I create just to return $self

      People have talked about trying to implement this kind of method-chaining in Moose, but currently the accessor generation code is still pretty hairy (the other contributors are actually hacking on it right now as I type), so the simpler way still is to use around.

      I think perhaps you need to step back and explain why you want to have so much 'magic' in your class? Do you not know the items that will be in BodyParts? Do you not know the definitions of it's sub-objects? Are you just looking to save on typing? Or perhaps I am misunderstanding your intentions completely and you need to show more code.

      -stvn
    Re: curried-up moose
    by pobocks (Chaplain) on Jan 18, 2009 at 02:53 UTC

      I'm a little confused by:

      $body = $body->legs( size=>42 )->eyes( color => blue);
      I mean, why do the legs make the eyes, knowhutimean?

      for(split(" ","tsuJ rehtonA lreP rekcaH")){print reverse . " "}print "\b.\n";
        pobocks,
        There is heated debate regarding chained methods as well as a method returning a reference to the object (good idea or not) but it works like this (traditional perl OO):
        sub legs { my ($self, $attribute, $val) = @_; $self->{legs}{$attribute} = $val; return $self; } sub eyes { my ($self, $attribute, $val) = @_; $self->{eyes}{$attribute} = $val; return $self; }

        In other words, the method returns the object so you can then invoke another method on the return value and then assign the return value of that chained method back to the original scalar holding a reference to the object. I made minor updates to this node soon after submitting as I hadn't seen the original code in the root thread.

        Cheers - L~R

          That is a remarkably clear summary, and you may consider me up to speed, and thankful for the explanation.

          I can see why there is debate over this; it seems vaguely distasteful to me, because it means that the code doesn't match the mental model of the thing it's modeling. On the other hand, the savings in typing must certainly make it attractive to many.

          for(split(" ","tsuJ rehtonA lreP rekcaH")){print reverse . " "}print "\b.\n";
    Re: curried-up moose
    by rodd (Beadle) on Jan 18, 2009 at 13:15 UTC
      Well pobocks, you're right, I guess having eyes attached to legs is not the best example. But basically I was just trying to exemplify the adding of parts to a body, then accessing those parts in any order with a single $body object instance. The package SOAP::Lite is a perfect example of stacking calls, even though I seldom use that kind of design.

      stvn, I think "subclasses" could be the right word, given eyes are a body-part and inherit the 'blood pressure' attribute from the body, even though they could be implemented as inner classes as well. But definitely currying is not what I meant, so I take it back. Anyway, I came to this question while prototyping proof-of-concept code in Moose, starting with object usage in the final code and a simple does-it all class. I was researching a way to represent a DOM-like objects quickly without so many subtypes and with as little keystrokes as possible. I didn't know yet where all the pieces would fall, so I might write the eyes code before I have a head or a leg for that matter.

      So this is not about my app design, which I've just started. It's that I really enjoy brainstorming with Moose, so after writing up around 20 lines of candidate has attributes, I saw myself writing another 20 arounds and, as a lazy programmer that I am, I was asking myself if I was missing something, on the lines of:

      has 'eyes' => ( is=>'rw', chained=>1, ... ); ## or has 'eyes' => ( is=>'rw', isa=>'Chained[HashRef]' );

      I'm glad to hear the Moose team is looking into that. At the end of the road, I'm just trying to replace my old style of prototyping with a big & ugly sub AUTOLOAD { ... } directive with something a little more readable and extensible, just in case the prototype prospers.
        I think "subclasses" could be the right word, given eyes are a body-part and inherit the 'blood pressure' attribute from the body,

        Actually, I would be more inclined to do that relationship with delegation instead of inheritance as i assume blood pressure would be an active changing value of the instance and not a static value of the class. Something like this perhaps:

        package CirculatorySystem; use Moose; has 'pressure' => (is => 'ro', isa => 'HashRef[Int]'); package Eyes; use Moose; has 'body' => ( is => 'rw', isa => 'Body', weak_ref => 1, # cycles are bad handles => [qw[ blood_pressure ]] ); package Body; use Moose; has 'circulatory_system' => ( is => 'ro', isa => 'CirculatorySystem', handles => { blood_pressure => 'pressure' } ); has 'eyes' => ( is => 'ro', isa => 'Eyes', trigger => sub { my $self = shift; # must hook up new eyes # to the body ... $self->eyes->body($self); } ); package main; my $nexus_6 = Body->new( eyes => Eyes->new, # i make your eyes! circulatory_system => CirculatorySystem->new( pressure => { systolic => 112, diastolic => 64 } ), ); print $nexus_6->blood_pressure; # is the same as ... print $nexus_6->circulatory_system->pressure; # is the same as ... print $nexus_6->eyes->blood_pressure;
        This way when the pressure of the circulatory system changes, it is reflected in the blood_pressure method of all the other body parts.

        ... so after writing up around 20 lines of candidate has attributes, I saw myself writing another 20 arounds and, as a lazy programmer that I am, I was asking myself if I was missing something,...

        I wonder if you are familiar with the array-ref versions of both has and around?

        has [qw[ eyes ears nose mouth ]] => (is => 'rw'); around [qw[ eyes ears nose mouth ]] => sub { ... code to make accessor +s return $self here ... }
        Of course you can't get very specific in your has definition, but if you are just prototyping it can be a very useful feature and is easily refactored later on. And also remember that the Moose sugar is only just perl functions, so you can just as easily do this:
        my @parts = qw[ eyes ears nose mouth ]; has $_ => ( is => 'rw', predicate => "has_$_" ) foreach @parts;
        The same also works for before/after/around as well.

        -stvn
          Nexus6! LOL! :D

          Thanks for the example. I completely ignored the usage and conceptual power of handles. In fact, handles is a dish I haven't learned to cook. Is there a recipe for it in Moose::Cookbook? Can't find it. Well, in any case the section on handles in the Moose.pm POD is extensive and I'm reading it over as I write this.

          BTW, the POD mentions a tree example built with handles that would become a recipe, but in the binary tree recipe handles are not used at all. Maybe this "build your own Nexus6" could become a recipe for it.

          around [qw eyes ears nose mouth ] => sub { ... code to make accessors return $self here ... }
          I had completely overlooked the existence and possibilities of the array-ref feature in prototyping. That's a great golf tip. Nice. Just one thing, it looks like
              around [qw[eyes ears nose mouth]]
          won't work. It should be just a straight array such as:
              around qw[eyes ears nose mouth]

          Anyway, your example of using handles for delegation will probably answer most of my chaining needs, but I'd still don't get the subtleties of using handles against using MooseX::CurriedHandles --something I'll look into carefully soon.

          Also, defining relationships --from body to eyes and from eyes to body-- seems a little wordy to me. How about this:

          package Body; use Moose; has 'eyes' => ( is=> 'rw', isa=> 'Eyes', metaclass=> 'MooseX::NestedPackage', class=> q{ has 'color'=> (is=> 'rw', default=> 'blue'); } ); package main; my $body=new Body(); ## gets new eyes too print $body->eyes; ## and they're blue $body->eyes(new Eyes(color=>'brown')); $body->eyes->color('green');

          Would this syntax be way left field? The extension looks like this (with some quite ugly eval's there).

          package MooseX::NestedPackage; use Moose; use Moose::Util; extends 'Moose::Meta::Attribute'; has 'class' => (is=> 'ro',isa=> 'Str'); after 'attach_to_class' => sub { my ($attr, $class) = @_; my $name = $attr->name; # 'eyes' my $class_name = $attr->{isa}; # 'Eyes' my $class_code = $attr->class; # all the antlers my $parent = $class->{package}; # 'Body' my $parent_lc = lc($parent); #'body' $attr->{trigger} = sub { my $self = shift; $self->$name->$parent_lc($self); }; my $code = qq{ package $class_name; use Moose; $class_code; has '$parent_lc' => (is=> 'rw',isa=> '$parent',weak_ref=> 1); }; eval $code; $@ && die qq{ $@:\n$code }; ## default is not needed $attr->{default} = sub{ $class_name->new() }; ## cos defaults should go directly in each nested attribute $attr->{lazy} = 0; ## lazy stopped working outside due to a missi +ng default };

          -rodrigo

    Re: curried-up moose
    by Jenda (Abbot) on Jan 21, 2009 at 23:42 UTC

      Waitasecond. Assuming you did not intend to say that the legs have some eyes, you want the $body->legs(...) to return the modified $body, right? So the $body->eyes will return the $body as well, right? So the $body->eyes->color returns the color of the body, not the color of the eyes, right? I guess not...

      So what does the $body->method(...) return? The modified $body or some inner object? Or sometimes one and sometimes the other?

      Method chaining works OK on objects that do not have any inner objects and whose method names make it very very clear which ones modify the object (and then return the modified $self) and which ones return some properties. As soon as it's not entirely clear, you're gonna run into problems.

      Maybe it would be better to use somethign like this instead:

      for ($body) { $_->legs(size=>42); $_->eyes(color => 'blue'); print $_->eyes->color; }
      (This is similar to the with(object) { .method(...); .method(...) } of some languages.)

      It's kinda shame you can't use something like

      for ($body) { -->legs(size=>42); -->eyes(color => 'blue'); print -->eyes->color; }
      as that would be easy to write and I think it would not clash with anything.

    Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Domain Nodelet?
    Node Status?
    node history
    Node Type: perlquestion [id://737084]
    Front-paged by Arunbear
    help
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others taking refuge in the Monastery: (3)
    As of 2022-10-03 11:46 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?
      My preferred way to holiday/vacation is:











      Results (13 votes). Check out past polls.

      Notices?