http://www.perlmonks.org?node_id=1150134


in reply to Re: Declaring with my, assigning, and testing in same line (custom routine)
in thread Declaring with my, assigning, and testing in same line

And for completeness , I'm sure you could also use variable :attributes for such checks.
It's possible, but not straightforward. Attributes alone can't do it, because they don't happen at runtime, so they don't see the assigned values:
my @v :Nonempty = keys %h;

Nonempty has no acces to the keys of %h. Fortunately, you can use attributes with tie. It's still not easy, though: the constructor (TIEARRAY) doesn't see the assigned values, either. STORE sees them, but it's not called when the right hand side list is empty! Fortunately, there's one method that gets called in any assignment: EXTEND:

#!/usr/bin/perl use warnings; use strict; { package Array::Nonempty::Attr; use Attribute::Handlers; sub Nonempty :ATTR(ARRAY) { tie @{ $_[2] }, 'Array::Nonempty' } } { package Array::Nonempty; use Tie::Array; use parent -norequire => 'Tie::StdArray'; use Carp; sub EXTEND { my ($self, $size) = @_; croak "Cannot be empty" if 0 == @$self && 0 == $size; $self->SUPER::EXTEND($size) } } use parent -norequire => 'Array::Nonempty::Attr'; my %hash_ok = ( answer => 42 ); my %hash_empty = (); my @keys_ok :Nonempty = keys %hash_ok; my @keys_empty :Nonempty = keys %hash_empty;

You can add other methods to check they wouldn't help you:

use Data::Dumper; sub STORE { warn Dumper('store', \@_); shift->SUPER::STORE(@_) } sub TIEARRAY { warn Dumper('tie', \@_); shift->SUPER::TIEARRAY(@_) } sub CLEAR { warn Dumper('clear', \@_); shift->SUPER::CLEAR(@_) }

To create an array that can never be empty, you'd have to implement SHIFT, POP, and SPLICE, too.

sub SPLICE { my $self = shift; $self->SUPER::SPLICE(@_); croak "Cannot be empty" if 0 == @$self } sub SHIFT { my $self = shift; croak "Cannot be empty" if 1 == @$self; $self->SUPER::SHIFT(@_) } sub POP { my $self = shift; croak "Cannot be empty" if 1 == @$self; $self->SUPER::POP(@_) }
($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

Replies are listed 'Best First'.
Re^3: Declaring with my, assigning, and testing in same line (attributes)
by LanX (Saint) on Dec 12, 2015 at 21:24 UTC
    Thanks I started experimenting myself after you messaged me.

    As said you'll need to tie cause Attributes happen at compile time and you have to intercept the assignment in STORE.

    (didn't know about EXTEND till now)

    I'm fine with using tie as long as I can untie when I'm done. Otherwise the penalty for this syntactic sugar would be to huge.

    I took a simpler case, tieing a scalar, and trying to use untie $$self within STORE {}. Didn't work. (probably because dereferencing isn't allowed for untie or scope restrictions)

    IMO as long we can't automatically untie, this approach shouldn't be used.

    update

    seems liked I didn't realy understood Tie::StdScalar yet

    update
    this approach with autotieref

    use Attribute::Handlers autotieref => { Selfish => Tie::Selfish };

    should provide the necessary reference to the tied structure, so that untieing becomes possible.

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

      You can tie in the attribute handler, so you can untie there, too. So, a closure sent as an extra argument to the constructor works:
      #!/usr/bin/perl use warnings; use strict; use feature qw{ say }; { package Array::Nonempty::Attr; use Attribute::Handlers; sub Nonempty :ATTR(ARRAY) { my $referer = $_[2]; tie @$referer, 'Array::Nonempty', sub { untie @$referer } } } { package Array::Nonempty; use Tie::Array; use parent -norequire => 'Tie::StdArray'; use Carp; sub CLEAR { my $self = shift; $self->[0] = []; } sub TIEARRAY { my ($class, $untie) = @_; bless [ [], $untie ], $class } sub EXTEND { my ($self, $size) = @_; croak "Cannot be empty" if 0 == @{ $self->[0] } && 0 == $size; $self->SUPER::EXTEND($size); # Prevent "untie attempted while 1 inner references still exis +t" my $untie = $self->[1]; undef $self; $untie->() } } use parent -norequire => 'Array::Nonempty::Attr'; my %hash_ok = ( answer => 42 ); my %hash_empty = (); my @keys_ok :Nonempty = keys %hash_ok; say tied(@keys_ok) // 'not tied'; say for @keys_ok; my @keys_empty :Nonempty = keys %hash_empty;
      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
        nice! :)

        see

        use Attribute::Handlers autotieref => { Selfish => Tie::Selfish };

        for another way for untie to access the varref

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

      > I'm fine with using tie as long as I can untie when I'm done. Otherwise the penalty for this syntactic sugar would be to huge.

      At second thought this is a too limited technique... consider

      while(1) { my $x :check = some_call(); }

      The check would be done only once, so in this case I'd clearly prefer a functional approach

      while(1) { check {RULE} my $x = some_call(); }

      Using attributes is a pretty way to apply ties, but most use cases I can think of combining this with untie are limited to debugging.

      > > > And for completeness , I'm sure you could also use variable :attributes for such checks.

      Wrong I was, young padawan. ;-)

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

        The check would be done only once
        My tests show otherwise:
        #!/usr/bin/perl use warnings; use strict; use feature qw{ say }; use Syntax::Construct qw{ // }; { package Array::Nonempty; use Tie::Array; use parent -norequire => 'Tie::StdArray'; use Carp; sub CLEAR { my $self = shift; $self->[0] = []; } sub TIEARRAY { my ($class, $untie) = @_; bless [ [], $untie ], $class } sub EXTEND { my ($self, $size) = @_; croak "Cannot be empty" if 0 == @{ $self->[0] } && 0 == $size; $self->SUPER::EXTEND($size); # Prevent "untie attempted while 1 inner references still exis +t" my $untie = $self->[1]; undef $self; untie @$untie; } } { package UNIVERSAL; use Attribute::Handlers autotieref => { Nonempty => 'Array::Nonemp +ty' }; } my @list = 1 .. 5; for (1 .. 10) { my @check :Nonempty = @list; shift @list; say tied(@check) // 'not tied'; say "@check"; }
        Wrong I was, young padawan. ;-)
        There are ways how not to be wrong.
        ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,