4

I have a

package Test;
use Moose;
has 'attr' => ( is => 'rw', isa => 'Str' );

Inside a method I'd like to apply a s/pattern/string/g on the attribute. For reasons documented in Moose (basically to properly support polymorphism) I do not want to access the $self->{attr} directly, so a simple:

$self->{attr} =~ s/pattern/string/g;

is not an option. How can I do this efficiently in speed and little but clear code with Moose?

Options I came up with are:

1) Use a temporary variable, and the usual getter/setter method:

my $dummy = $self->attr;
$dummy =~ s/pattern/string/g;
$self->attr($dummy);

2) Using the attr getter/setter on the left hand side:

$self->attr($dummy) =~ s/pattern/string/g;

But this obviously throws an error

Can't modify non-lvalue subroutine call at Test.pm line 58, line 29

Is there a way to use Moose accessors as lvalue subs?

3) Use the String traits

Redefine the attribute:

has 'attr' => ( is => 'rw', isa => 'Str', traits  => ['String'],
                handles => { replace_attr => 'replace'}  );

Then in the method use:

$self->replace_attr('pattern', 'string');

However the docs explicitly say, there's no way to specify the /g flag.

Any elegant, simple, somewhat efficient method available out of the box?

cfi
  • 10,915
  • 8
  • 57
  • 103
  • Related: [Proper way to use a Moose class attribute in a regex?](http://stackoverflow.com/q/32613115/176646) – ThisSuitIsBlackNot Sep 22 '15 at 14:45
  • Why do you want to do that? Maybe implementing it as an `around` modifier for the writer or as a trigger makes more sense than having it somewhere else? – simbabque Sep 22 '15 at 15:06
  • If the accessor was an lvalue sub, it would effectively access `$self->{attr}` directly unless magical return values were created, slowing every down every access that doesn't need it. – ikegami Sep 22 '15 at 15:41

2 Answers2

5

I have used this approach in the past and I think it seems suitable to me for general use in terms of efficiency and cleanliness. It also works with the /g modifier.

$self->attr( $self->attr =~ s/pattern/string/gr );

I suspect that under the hood this is the same as your first example with the temporary variable, it is just hidden from us.

Please note that the to use the /r modifier, which returns the result of the substitution without modifying the original, requires Perl 5.14+.

Hunter McMillen
  • 59,865
  • 24
  • 119
  • 170
  • This is shorter than my listed attempts, and thus a good suggestion. If we could avoid the code redundancy, that would be good. – cfi Sep 22 '15 at 14:49
  • Even though I went with my own answer as a solution, and even though I did not (and currently will not) do a performance benchmark, I mark yours as the solution, because it is not a wise idea to pay a performance penalty for every access when all we get is a little less coding redundancy. While I am strongly opposing such redundancy, this one is very local (two attr names right next to each other and well maintainable. – cfi Sep 23 '15 at 20:35
4

My Option (2) and this question provide the idea to use MooseX::LvalueAttributes:

package Test;
use Moose;
use MooseX::LvalueAttribute 'lvalue';
has 'attr' => ( is => 'rw', isa => 'Str', traits => [lvalue] );

This allows the straightforward syntax:

$self->attr($dummy) =~ s/pattern/string/g;

Internally this uses Variable::Magic and the perlsub lvalue feature, so there is a performance overhead to this approach which affects every access to the 'traited' attribute, not just the ones where it's used as a left hand side. Thanks to LeoNerd and ikegami for their correcting comments on my earlier statements.

Therefore, and confirmed by the module's documentation, Moose's type checking still works and triggers are fired.

Community
  • 1
  • 1
cfi
  • 10,915
  • 8
  • 57
  • 103
  • Um, it does? At latest reading just now (version 0.981) claims that all the validation and triggers work just fine. – LeoNerd Sep 22 '15 at 16:35
  • @LeoNerd is right. It returns a magical scalar instead of returning `$self->{attr}` directly in order to properly do stuff like type checking. I believe this significantly adds to the cost of the accessing the attribute. – ikegami Sep 22 '15 at 18:11
  • @LeoNerd and ikegami: What a blatant blunder: I read the docs and wrote the opposite. Thanks for correcting me, I've updated the answer. – cfi Sep 23 '15 at 06:57