Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

$foo->bar = 14;

by Juerd (Abbot)
on Dec 29, 2002 at 19:25 UTC ( [id://222941]=perlmeditation: print w/replies, xml ) Need Help??

Traditionally, object properties in Perl are retrieved and set either through methods or by using the object as the reference it is.

To automate the creation of accessor methods, I don't use a module, but a simple closure creating loop like this one:

BEGIN { no strict 'refs'; for my $property (qw/id debug display_time default_text/) { *$property = sub { my ($self, $value) = @_; croak "Too many arguments for $property method" if @_ > 2; $self->{$property} = $value if @_ == 2; return $self->{$property}; } } }

In #perlhelp (efnet), xmath pointed out that methods can be used as lvalues (with the lvalue attribute). It appears attributes work on anonymous subs. That knowledge combined leads to something like:

BEGIN { no strict 'refs'; for my $property (qw/id debug display_time default_text/) { *$property = sub : lvalue { $_[0]->{$property} }; } }

So that means:
OLD SITUATION NEW SITUATION $foo->bar; $foo->bar; $foo->bar(15); $foo->bar = 15; { my $temp = $foo->bar; $temp =~ s/foo/bar/; $foo->bar($temp); } $foo->bar =~ s/foo/bar/

Before I'm going to change my coding style, I'd like to know what the community thinks of it. The lvalue attribute is experimental.

Note that I know this doesn't check the new values. Neither did my old code - I don't really care :) And no, I don't like using the hash reference for my external API.

So, what do you think?

- Yes, I reinvent wheels.
- Spam: Visit eurotraQ.

Replies are listed 'Best First'.
Re: $foo->bar = 14;
by sauoq (Abbot) on Dec 29, 2002 at 20:48 UTC

    This must be one of those hundredth monkey things. I was just having a discussion with BrowserUk yesterday about lvalue closures. I was going to write it up and ask if anyone was actually using them for anything useful. Maybe I still will after I put some more thought into it.

    The contrived example I was working with was a settable counter that would increment when accessed. Using an lvalue closure resulted in nicer syntax than a closure which returns a reference. Returning a reference allows the value to be settable but it also requires dereferencing when you just want to use the value and that's pretty yucky. Of course, there are other ways to do it too, such as a tied scalar.

    It seems to me that, although really neato, it doesn't provide a whole lot outside of some slimmer syntax. I think the cases where the syntax is significantly leaner (such as your $foo->bar =~ s/foo/bar/ example) are probably pretty rare.

    Given the experimental implementation of the lvalue attribute, I won't use it in production code. Right now, it's a toy to play with. I'm still undecided as to whether (or how often) I'd use it if it weren't experimental.

    -sauoq
    "My two cents aren't worth a dime.";
    
      I've been very lvalue-oriented for some time now, and I don't see anything wrong with the lvalue feature that would prevent you from using it in production code. It works just as you'd expect, though you have to use it properly.

      The reason it's probably considered experimental is likely because it's not fully implemented, as in, to the logical conclusion of what an lvalue-function should be. While the basic functionality seems rock solid, there's limitations to what you can do with it, such as performing data validation. The current take on the lvalue problem is likely to be deprecated and/or eliminated in Perl 6, but then again, a lot of things are, so I'm not worried.

      One of the benefits of using subroutines instead of direct hash access is that you can catch typos easily. If you assign to $self->{elemnent} you're missing the mark quietly, but a call to $self->elemnent will outright fail.

      I think it's a mistake to confuse "experimental" as a synonym for "doesn't work properly yet".
        I think it's a mistake to confuse "experimental" as a synonym for "doesn't work properly yet".

        For me experimental means:

        1. Might have bugs - probably not true for :lvalue, the current implementation seems pretty solid
        2. Might change in a non-upward compatable manner - more likely for :lvalue if it is going to be of much use
        3. Might not be the "right" way of doing it - in my opinion the current :lvalue opens up your object implementation details far to much and is not the "right" way :-)

        I would not use :lvalue in production code because of (2) and (3). See lvalue considered harmful... where I go into this in more boring detail :-), and a suggestion for how I would like it to work.

        One of the benefits of using subroutines instead of direct hash access is that you can catch typos easily. If you assign to $self->{elemnent} you're missing the mark quietly, but a call to $self->elemnent will outright fail.

        I agree, but I think the disadvantages of :lvalue more than outweigh this advantage (and there are other ways to get around typos - e.g. inside out objects or Tie::SecureHash.)

        I think it's a mistake to confuse "experimental" as a synonym for "doesn't work properly yet".

        I agree. I use it as a synonym for "is subject to change without warning." However unlikely, I don't want an unexpected change in an experimental feature to break production code. I like to think of that as being prudent though some might describe it as being paranoid. In any case, my operational experience has taught me that "experimental" and "production" don't mix.

        -sauoq
        "My two cents aren't worth a dime.";
        
Re: $foo->bar = 14;
by Aristotle (Chancellor) on Dec 29, 2002 at 20:30 UTC
    As shotgunefx pointed out, there's unfortunately no way to constrain the value assigned in anyway, neither before nor after the assignment. That renders the attribute nearly useless as far as I'm concerned.. a pity, since it'd be a lovely way to offer a bit of syntactical sugar. You could even support both the ->bar("baz") and the ->bar = "baz" style at the same time, but that's a moot consideration as is. :(

    Makeshifts last the longest.

Re: $foo->bar = 14;
by shotgunefx (Parson) on Dec 29, 2002 at 20:21 UTC
    Personally I like the lvalue style, I've been experimenting with it myself lately. It's too bad that you can't hook the assignment (without tieing) though. For certain things, I think it makes a lot of sense, though I don't plan on abandoning $foo->bar('base') method anytime soon.

    -Lee

    "To be civilized is to deny one's nature."
Re: $foo->bar = 14;
by BrowserUk (Patriarch) on Dec 29, 2002 at 23:32 UTC

    I had similar ideas regarding lvalue subs for accessor/mutator methods (see: (my?) problem with re-blessed references(?)), and I still find the idea appealing. The limitations of the current experimental implementation of lvalue subs render them such that for anything other than trivial cases, the limitations outway the benefits currently.

    As I described in What the [sub]in 'L value do they have?, I thought I saw ways to improve the current implementation, but it may be that doing so would limit or corrupt their existing utility.

    I haven't yet managed to wrap my head around how the sub could be given access to the assigned value in

     $foo->bar =~ s/a/b/g;

    prior to assignment, without destroying the efficiency and utility of that construct.

    One thought that does come to mind is that lvalue accessor/mutator methods could be used internally to a class for accessing their own instance data in true OO-style, but that the published interface would not expose those methods. This would retain the benefits of minimising changes within the class if the structure of the instance data changed, but would also remove some of the overhead in having non-mutator methods access instance data via them.

    I just re-read that and whilst it says what I am trying to say, it's about as clear as mud and I don't see how to easily improve it. Perhaps a simple example.

    #! perl -slw use strict; package Message; use constant CLASS => 0; use constant SELF => 0; use constant TEXT => 1; use constant TS => 1; use constant ATTRIBUTES => qw/_id _timestamp text/; BEGIN { no strict 'refs'; for my $property (ATTRIBUTES){ *$property = sub : lvalue { $_[SELF]->{$property}; } } } { my $nextID = 0; sub new { my $self = {}; @$self{'_id', '_timestamp', 'text'} = ($nextID++, time(), $_[T +EXT] || 'Unassigned!'); return bless $self, $_[CLASS]; } } sub ID { $_[SELF]->_id; } sub timeStamp { $_[SELF]->_timestamp } sub setTS { $_[SELF]->_timestamp = $_[TS] if $_[TS] and $_[TS] =~ /^\d ++/; } sub stringify{ sprintf '<' . __PACKAGE__ . '>' . $/ . '<id value="%04d" />' . $/ . '<timestamp value="%010d" />' . $/ . '<message-text>%s</message-text>' . $/ . '</' . __PACKAGE__ . '>' . $/, map{ $_[SELF]->$_ } ATTRIBUTES; } package main; my $msg = new Message 'A message'; print $msg->stringify(); $msg->text = 'This is the new message text'; print $msg->timeStamp; $msg->setTS( time()-1000 ); print $msg->stringify(); my $msg2 = new Message; print $msg2->stringify(); $msg2->text = 'Yet another test message'; print $msg2->stringify(); __END__ C:\test>lvalue <Message> <id value="0000" /> <timestamp value="1041200593" /> <message-text>A message</message-text></Message> 1041200593 <Message> <id value="0000" /> <timestamp value="1041199593" /> <message-text>This is the new message text</message-text></Message> <Message> <id value="0001" /> <timestamp value="1041200593" /> <message-text>Unassigned!</message-text> </Message> <Message> <id value="0001" /> <timestamp value="1041200593" /> <message-text>Yet another test message</message-text> </Message>


    Examine what is said, not who speaks.

      Oh. Can anyone explain why this doesn't work in the context of the sample above?

      # Substitute for line 22 in the sample above. @$self{ATTRIBUTES} = ($nextID++, time(), $_[TEXT] || 'Unassigned!');

      Examine what is said, not who speaks.

Re: $foo->bar = 14;
by dws (Chancellor) on Dec 29, 2002 at 21:40 UTC
    Before I'm going to change my coding style, I'd like to know what the community thinks of it.

    One problem with the approach you set forth is that it doesn't survive subclassing.

    Personally, I don't favor automatic creation of accessors (or mutators) at runtime. Development-time code generation can be good, but my experiences with delaying generation until runtime have almost always led to a debugging quagmire. I want accessors/mutators reflected directly in the source.

      One thing I like it for is CGI.
      I would rather say $cgi->param('foo') = 'bar' than $cgi->param(-name=>'foo',-value=>'bar')


      -Lee

      "To be civilized is to deny one's nature."

      Personally, I don't favor automatic creation of accessors (or mutators) at runtime.

      I have this creation wrapped in BEGIN blocks, and have always been able to subclass them. Not that it matters much, because a module is run completely when it is used (how else can its return value be evaluated?). Inheritance itself is a run-time thing, so I can't see how it can break. Note that I'm not using AUTOLOAD here.

      - Yes, I reinvent wheels.
      - Spam: Visit eurotraQ.
      

      One problem with the approach you set forth is that it doesn't survive subclassing.

      Eh?

      use strict; use warnings; { package Foo; sub new { bless {}, shift }; BEGIN { no strict 'refs'; for my $property (qw/foo bar/) { *$property = sub : lvalue { $_[0]->{$property} }; } } } { package Bar; use base qw(Foo); BEGIN { no strict 'refs'; for my $property (qw/fribble ni/) { *$property = sub : lvalue { $_[0]->{$property} }; } } }; my $o = Bar->new; $o->foo=1; $o->bar=2; $o->fribble=3; $o->ni=4; print join(', ', $o->foo, $o->bar, $o->fribble, $o->ni), "\n"; # produces # 1, 2, 3, 4

      or am I missing something?

        or am I missing something?

        No, I missed something. I was reacting based on past misadventures with something very similar, without having read your code carefully enough. What you've written will indeed survive subclassing, though debugging through those generated methods is still going to be a problem. Mea culpa.

Re: $foo->bar = 14;
by adrianh (Chancellor) on Dec 29, 2002 at 20:49 UTC
Re: $foo->bar = 14;
by BrowserUk (Patriarch) on Dec 29, 2002 at 20:56 UTC

    I'm getting

    syntax error at C:\test\lvalue.pl line 10, near "$property :" BEGIN not safe after errors--compilation aborted at C:\test\lvalue.pl +line 10.

    from

    BEGIN { no strict 'refs'; for my $property (qw/id debug display_time default_text/) { *$property : lvalue = sub { $_[SELF]->{$property} }; } }

    What am I doing wrong?


    Examine what is said, not who speaks.

      I think the proper syntax would be:

      *$property = sub : lvalue { ... }

      Update: It appears Juerd has fixed it in his original post now too.

      -sauoq
      "My two cents aren't worth a dime.";
      
Re: $foo->bar = 14;
by pg (Canon) on Dec 29, 2002 at 20:49 UTC
    It is controversial whether one should use the pseudo-hash, but if you are using it, and now would like to plug in your code, then just get the valid field names from pseudo-hash, instead of using a seperate qw.
Re: $foo->bar = 14;
by John M. Dlugosz (Monsignor) on Dec 30, 2002 at 21:45 UTC
    OK, the reason you use "automated" accessors instead of using the hash directly is so that the accesor sould be changed at some point without messing up the clients. The internal representation might change, or you might need to hook either the get or the set (or both) to perform other actions, such as keeping the state in sync or reporting debug events.

    Having the lvalue return the hash element means the assignment, the setter part, is not under your control. You can't change the code behind setting the value (if the representation changed, for example) without affecting the callers. The only thing it does is let you change the name of the hash element (big deal), or hook "access" without caring whether it will be a get or set and without knowing the new value if a set.

    So, I don't use it, since it doesn't do what I want of an accessor.

    —John

Re: $foo->bar = 14;
by hypochrismutreefuzz (Scribe) on Jan 04, 2003 at 10:20 UTC

    Another way to encapsulate a property is with a psuedo hash. The fields pragma gives the $self variable the instance variables and the new() returns a blessed reference. Then, another psuedo hash is made, $prop, that acts as a static variable that maps the property names to code references. Then the method 'property' is called thus: $obj->property('propertyname', 'value' ...);

    The example I give uses a colorref type of variable that takes a red, blue, and green integer values and returns a hex value that you could put in an HTML attribute, say.
    package ClassExample; use strict; use warnings; use fields qw/color texture/; my $prop = fields::phash( [qw/color texture/], [\&color_prop, \&texture_prop] ); sub create { my $class = shift; my $self = fields::new($class) unless ref($class); return $self; } sub property { my $self = shift; my ($propname, @params) = @_; $self->{$propname} = &{ $prop->{$propname} }(@params) if @params; return $self->{$propname}; } # these subroutines could be isolated in another module and imported # PRIVATE!!! keep out! sub color_prop { my %colors = @_; my $ok = 0; for (qw/red green blue/) { if (exists $colors{$_}) { next if $colors{$_} < 0; next if $colors{$_} > 0xff; $ok++; } } die "@_ is not a red/green/blue color ref" unless $ok == 3; return sprintf('%02X%02X%02X', $colors{red}, $colors{green}, $colo +rs{blue}); } sub texture_prop { my $texture = shift; my $ok = 0; for (qw/rough smooth crinkly/) { if ($texture eq $_) { $ok = 1; last; } } die "$texture is not rough/smooth/crinkly texture value" unless $o +k; return $texture; } 1; # text_example.pl #!/usr/local/bin/perl use strict; use warnings; use ClassExample; my $clex = ClassExample->create(); $clex->property('color', 'green', 0xde, 'red', 255, 'blue', 1); $clex->property('texture', 'rough'); my $printthing = <<"end_of_print"; Properties of %s: Texture: %s Color: %s end_of_print print "\n"; printf ($printthing, '$clex', $clex->property('texture'), $clex->prope +rty('color')); my $texture = $clex->property('texture', 'smooth'); my $colorref = $clex->property('color', red=>0x52, green=>0x20, blue=>0x11); printf ($printthing, '$clex', $texture, $colorref);

    By running test_example.pl you get the output

    e:\Borland\Programs>test_example.pl
    
    Properties of $clex:
    Texture: rough
    Color:   FFDE01
    
    Properties of $clex:
    Texture: smooth
    Color:   522011
    

    I tested it using ActiveState Perl 5.8 under Windows 2000.

Re: $foo->bar = 14;
by Arien (Pilgrim) on Dec 31, 2002 at 05:44 UTC
    And no,I don't like using the hash reference for my external API.

    Which is exactly what you are doing when you think about it, isn't it? ;-) As a result using lvalue breaks encapsulation just as bad as allowing (the other, more common way of) direct access to your attributes.

    As it is, I see no use for lvalue.

    — Arien

Re: $foo->bar = 14;
by jimc (Sexton) on Jan 03, 2003 at 16:09 UTC
    lvalue methods are a questionable idea, at least if used universally.

    an lvalue method just exposes a variable (ie the storage for) to the assignment, it runs b4 the assignment is done.

    It gets no chance to validate the RHS, thus you could accidentally do:

    $person->age = -1;
    $person->name = '2/22/01';
    $person->ssn = 'capt. bob';

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (7)
As of 2024-03-19 11:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found