in reply to Are lvalue methods (as properties) a good idea?

I'm not too fond of them. Currently, there is no clean way to validate the value:

$object->digits = [qw/finger finger thumb/]; # whoops!

One can use Attribute::Property for that, but then you're relying on a attributes and those rely on CHECK blocks and CHECK blocks don't run under all environments (mod_perl, for example). So if you want to use lvalues, feel free, but just be leery of using them in code that you distribute to others as they are rather fragile.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re^2: Are lvalue methods (as properties) a good idea?
by Zaxo (Archbishop) on Jan 12, 2005 at 22:33 UTC

    It's relatively clean to make the sub a closure on a tied scalar, where the STORE() method does the checking. That succeeds in highjacking assignment.

    I gave an example in To Validate Data In Lvalue Subs. That still seems to me like a clean and flexible approach to validation. As mentioned in that thread, Attribute::Properties uses this technique under the hood, but as you say the tie-in to an attributes interface reduces its applicability.

    After Compline,
    Zaxo

      Wow. That's one heck of a lot of overhead to support syntactic sugar :) When you have to make the sub a closure and tie it to a custom class to provide validation for lvalue attributes, I'm at a loss to see how this is preferable to:

      sub bar { my ($self, $bar) = @_; croak "bar() requires a hashref" unless 'HASH' eq ref $bar; $self->{bar} = $bar; return $self; }

      Cheers,
      Ovid

      New address of my CGI Course.

        I'm at a loss to see how this is preferable to:

        Juerd has a node that demonstrated this nicely, but I cannnot find it right now. Compare:

        $obj->bar++; $obj->bar =~ s[a][b]g; $obj->len++ while substr( $obj->bar, $obj->start, $obj->len ) =~ m[^\s ++$]; substr( $obj->bar, $obj->start, $obj->len, ''); $obj->len = 0;

        with:

        $obj->bar( $obj->$bar() + 1 ); my $temp = $obj->bar(); $temp =~ s[a][b]g; $obj->bar( $temp ); my $temp2 = $obj->len; my $temp3 = $obj->bar; $temp2++ while substr( $temp3, $obj->start(), $temp2 ) =~ m[^\s+$]; substr $temp3, $obj->start, $temp2, '' ); $obj->bar( $temp2 ); $obj->len( $temp3 );

        Another way of looking at it. Would you prefer doing math like this?

        ## $a++; $a( $a() + 1 ); # $b *= $c-- $d++; $b( $b() * $c( $c() - 1 ) % $d( $d() + 1 ) );

        Examine what is said, not who speaks.
        Silence betokens consent.
        Love the truth but pardon error.

        I like the ability to use objects (and thus functions which return objects) as lvalues. A bit of my old C++ days sneaking up on me. But I won't use that until Perl6 when you can actually set a function to be the "set" routine, and a function as the "get" routine. I like the syntax of $obj->foo = 3, but I definitely need to be able to easily (that is, with little overhead) trap that to do something else with it - whether that's validate the new value, set a marker for change (that represents a dirty object for persistance), or send out events to listeners. Or even, heaven forbid, change my underlying object such that foo now represents something else. About the only thing I like about Perl 5's lvalue attributes is that they were something that taught Perl6's designers what was really needed :-)

        Sure it's overhead. But you only need to write the tieing class once globally, and you only need to write one validating closure per kind of validation. With that work out of the way, you can define the constraints for all your attributes declaratively.

        As far as I'm concerned it's a big win in terms of syntax and maintenance.

        Makeshifts last the longest.

      Class::Accessor::Lvalue also allows you to create them for a class and it has the nice feature that a call to $a->wibble = 5 gets translated to $a->set_wibble(5) so all your usual verification code is still callable (or you can choose to autogenerate a plain old setter with Class::Accessor).

      In my own code I use my own method maker module (not on CPAN because there are already enough method makers on CPAN) that does something similar but slightly unusual. Doing

      package Foo; use Fergal::MM qw( Bar );
      will create a setBar and getBar pair of methods and also a Bar lvalue method which just calls setBar and getBar. So far, so normal. What's unusual is that it puts the methods into Foo::Methods and makes Foo inherit from Foo::Methods so that if I want to have something special (validation etc) happen when a setter or getter is called. I just do
      package Foo; use Fergal::MM qw( Bar ); sub setBar { my $self = shift; my $value = shift; check($value); $self->SUPER::setBar($value); }
      Yes, there's plenty of overhead in this but usually it doen't make any difference to me.

      Another interesting thing I do with lvalue accessors is to add indexed accessors. So I can do something like

      $a->Grid(2, 3) = $player1; $a->Grid(7, 5) = $player2;
      this gets translated into
      $a->setGrid($player1, 2, 3); $a->setGrid($player2, 7, 5);
      so by writing the correct setters and getters, you can create attributes that act like multidimensional arrays or hashes and that are also overrideable by sub classes and can have validation etc.

        That is very cool. If you don't want to put this on CPAN, maybe you could talk to the author(s) of (an) existing module(s) and see if they wouldn't like to support for such?

        Makeshifts last the longest.