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

Hi,
I have an OO method which should return/set a value stored in one hash of the object (not the blessed object hash). In order to have multiple ways to do it I'm using :lvalue to allow the assignment of a value to the method call, i.e.:
$obj->header('My Header', 'value'); # Set $obj->header('My Header') = 'value'; # Set if ($obj->header('My Header') eq 'value' ) { # Get ...
Know the overkill: When the method is called without an argument (except $self) it should return the internal header hash as ref, as rvalue would be enough.

I know the return statement must be omitted in order to make lvalue work. Because subs return then the return value of the last statement I thought I just put the two different return values at the end of a if () { } else { } statement. This returns an error message (Can't return a temporary from lvalue subroutine), so I put in a return statement for the hash ref.
Now it's working: lvalue for hash element, rvalue for whole hash. The code looks like this:

sub header : lvalue { my ( $self, $h, $value ) = @_; if ( @_ == 1 ) { return $self->{header}; } $self->{header}->{$h} = $value if defined $value; $self->{header}->{$h}; }
My question now: Is this clean enough or already a dirty trick or "black magic"? I would love to hear the opinion from a Perl guru with deeper insight in lvalue subs.

Thanks

Replies are listed 'Best First'.
Re: Mixed lvalue/rvalue sub function
by kyle (Abbot) on Apr 18, 2008 at 15:24 UTC

    If you want this kind of interface, I'd recommend you use overload and present the object as a hash. Have a look at Make an inside-out object look hash-based using overload. for an example of what I'm talking about. You can have code that looks like this:

    $obj->{header}{'My Header'} = $value; if ( $obj->{header}{'My Header'} eq 'value' ) # ...

    ...and behind the scenes it can call whatever instance method you want.

    That said, this is all a bit more than is really necessary. I wouldn't put this kind of interface on any new code, but it's not horrific enough for me to want to remove it from working code either.

Re: Mixed lvalue/rvalue sub function
by elmex (Friar) on Apr 18, 2008 at 13:23 UTC

    I'm certainly not a guru, and didn't even knew or realized that 'lvalue' exists. So thanks for bringing it up :)

    Aside from that, just my opinion: That kind of API is quite weird. I usually write such methods like you did, with an optional $value argument. I guess what I dislike about that lvalue API is, that it looks like action at a distance when skimming through code:

    my $v = $self->header ('test') = "fail"; # :-)
    vs.:
    my $v = $self->header ('test', 'fail');

    When you skim over the second example you immediately recognize the missing closing ')'. Of course the first example is also an example of bad formatting, so this might not be an actual issue. I'm just not used to assigning to subroutines.

    But that aside: Did you think about returning object handles where you overload the '='? In C++ that is done quite often. Then even this would work:

    my $hdr_slot = $self->header ('test'); $hdr_slot = "new value"; # Set print "New header: $hdr_slot\n"; # Get
      Hi, I realize that the lvalue syntax looks a little weird, maybe I should kick it out and just have:
      $obj->header('test'); # Get header 'test' $obj->header('test','new value'); # set header test $obj->header; # Get all headers as hash
      You C++-ish suggestion:
      my $hdr_slot = $self->header ('test'); $hdr_slot = "new value"; # Set print "New header: $hdr_slot\n"; # Get
      doesn't work in Perl because there all objects are references (to talk C++: from type *myclass not myclass). So assigning to $hdr_slot would make this variable a string with the value "new value" and would remove the reference to the object. Overloading the '=' operator only is used for generating temp copies for operators like '+=', so far I know.

      The only way to do this is to tie a class to $hdr_slot. Then you can define a STORE method which gets called at every '='. See 'perldoc perltie' for more.

      I read all this in the book 'Object Oriented Perl' just two weeks ago.

        Ah, yes, you are of course completely right, I guess I did too much C++ recently :)

        Alternatively you could return a reference to the header, of course. But it would look kinda more ugly than the simple optional argument solution:

        my $slot = $self->header ('test'); $$slot = "New value"; print "Value for header: $$slot"; # or even $slot, if you overload the + stringification