Skip Menu |

This queue is for tickets about the Variable-Magic CPAN distribution.

Report information
The Basics
Id: 74453
Status: rejected
Priority: 0/
Queue: Variable-Magic

People
Owner: Nobody in particular
Requestors: pshangov [...] yahoo.com
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: 'store' callback for hashes called when reading values
When trying to implement a read-only hash with Variable::Magic, I have noticed that the 'store' callback gets invoked when using an array slice of the hash, or when iterating over an array reference contained within the hash (see the attached test.pl). I presume this is due to some internal state of the variable changing, but it is possible to distinguish between such changes and actual attempts to change the value of a hash key?
Subject: test.pl
use 5.010; use strict; use warnings; use Variable::Magic qw(wizard cast); my $wizard = wizard( store => sub { warn "Kaboom! [$_[2]]" } ); my %hash = ( foo => 'bar', baz => [qw(qux quux)] ); cast( %hash, $wizard ); say $_ for @hash{qw(foo baz)}; # Kaboom! say $_ for @{$hash{baz}}; # Kaboom!
Le Ven 27 Jan 2012 12:25:09, pshangov a écrit : Show quoted text
> When trying to implement a read-only hash with Variable::Magic, I have > noticed that the 'store' callback gets invoked when using an array slice > of the hash, or when iterating over an array reference contained within > the hash (see the attached test.pl). I presume this is due to some > internal state of the variable changing, but it is possible to
distinguish Show quoted text
> between such changes and actual attempts to change the value of a hash > key?
The hash slice is causing a 'store' magic call because "for" aliases $_ to the elements of the list, and $_ can possibly be assigned to in the body of the loop. This is the same mechanism that causes "1 for $hash{nonexistent_key}" to vivify : perl cannot know at compile time whether the hash slot will actually be need, so in doubt it creates the entry at the beginning of the loop. In that regard, it is "correct" for Variable::Magic to call the magic callback for this construct (in that it is as correct as perl itself). If you want to recognize those constructs, you can use the 'op_info' feature : use 5.010; use strict; use warnings; use Variable::Magic qw<wizard cast VMG_OP_INFO_OBJECT>; use Carp 'carp'; my $wizard = wizard( store => sub { my $op = $_[-1]; my $name = $op->name; my $flags = $op->flags; my $priv = $op->private; if ($name eq 'hslice' and $flags & 16 and $flags & 128 and not $priv) { carp("store '$_[2]'"); } }, op_info => VMG_OP_INFO_OBJECT, ); my %hash = (baz => [ qw<qux quux> ]); cast %hash, $wizard; @hash{qw<foo>} = 'bar'; # store { my @wut = local @hash{qw<foo bar>}; # nothing } say for @hash{qw<foo baz>}; # nothing say for @{$hash{baz}}; # nothing Vincent.
Subject: op_info_hslice.pl
use 5.010; use strict; use warnings; use Variable::Magic qw<wizard cast VMG_OP_INFO_OBJECT>; use Carp 'carp'; my $wizard = wizard( store => sub { my $op = $_[-1]; my $name = $op->name; my $flags = $op->flags; my $priv = $op->private; if ($name eq 'hslice' and $flags & 16 and $flags & 128 and not $priv) { carp("store '$_[2]'"); } }, op_info => VMG_OP_INFO_OBJECT, ); my %hash = (baz => [ qw<qux quux> ]); cast %hash, $wizard; @hash{qw<foo>} = 'bar'; # store { my @wut = local @hash{qw<foo bar>}; # nothing } say for @hash{qw<foo baz>}; # nothing say for @{$hash{baz}}; # nothing
Thanks for the exhaustive reply. Unfortunately I will have to ask another question demonstrating my ignorance with regard to perl's internals, but I've found the docs for B::OP discouraging. Hash::Util::lock_hash(), which I assume is built on the same type of magic, seems to be able to distinguish between: 1 for @hash{qw<foo baz>}; # lives and $_ = 1 for @hash{qw<foo baz>}; # dies Is this possible to implement with Variable::Magic too? Thanks, --Peter
Subject: op_info_hslice.pl.txt
use 5.010; use strict; use warnings; use Variable::Magic qw<wizard cast VMG_OP_INFO_OBJECT>; use Hash::Util qw(lock_hash); use Carp 'croak'; my $wizard = wizard ( store => sub { my $op = $_[-1]; my $name = $op->name; my $flags = $op->flags; my $priv = $op->private; if ($name eq 'hslice' and $flags & 16 and $flags & 128 and not $priv) { croak("store '$_[2]'"); } }, op_info => VMG_OP_INFO_OBJECT, ); my %magic_hash = my %locked_hash = ( foo => 'bar', baz => [ qw<qux quux> ] ); lock_hash %locked_hash; cast %magic_hash, $wizard; 1 for @{$magic_hash{baz}}; # lives $_ = 1 for @{$magic_hash{baz}}; # lives 1 for @magic_hash{qw<foo baz>}; # lives $_ = 1 for @magic_hash{qw<foo baz>}; # lives 1 for @{$locked_hash{baz}}; # lives $_ = 1 for @{$locked_hash{baz}}; # lives 1 for @locked_hash{qw<foo baz>}; # lives $_ = 1 for @locked_hash{qw<foo baz>}; # DIES
Subject: Re: [rt.cpan.org #74453] 'store' callback for hashes called when reading values
Date: Tue, 31 Jan 2012 18:13:43 +0100
To: bug-Variable-Magic [...] rt.cpan.org
From: Vincent Pit <perl [...] profvince.com>
On 31/01/2012 16:55, Peter Shangov via RT wrote: Show quoted text
> Hash::Util::lock_hash(), which I assume is built on the same type of > magic, seems to be able to distinguish between: > > 1 for @hash{qw<foo baz>}; # lives > > and > > $_ = 1 for @hash{qw<foo baz>}; # dies > > Is this possible to implement with Variable::Magic too? > > Thanks,
Hash::Util::lock_hash() does not work that way, and does not give special treatment to the aliasing behaviour of "for". It just makes the hash and all its values read-only. As you would have seen if you had put the "$_ = 1" assignment on its own line, use Hash::Util 'lock_hash'; my %hash = (a => 1, b => 2); lock_hash(%hash); for (@hash{qw<a b>}) { $_ = 3; } dies on line 5, which is the line of the assignment, and this happens because $_ (which aliases a value of the hash) is read-only. In the end, I am not sure what you want to achieve. If you want to distinguish between a "true" hash slice assignment and the lvalue context induced by iterating over the values of the slice, I have described a way to do this in my previous message, yet I strongly advise you against using it as I think it is a bad design decision. perl evaluates the list onto which a loop iterates in lvalue context for a good reason. Vincent.
Thanks for the explanation. I needed this for MooseX::Params, a module that provides subroutine signatures via attributes, and then makes the arguments available in a magic read-only hash %_. I wanted to allow users to write this: sub doit :Args(ArrayRef foo) { say for @{$_{foo}}; } While still preventing sub doit :Args(ArrayRef foo) { $_ = ... for @{$_{foo}}; } I think I will just update the docs explaining why neither is allowed. Thanks again, --Peter
Subject: Re: [rt.cpan.org #74453] 'store' callback for hashes called when reading values
Date: Tue, 31 Jan 2012 19:17:55 +0100
To: bug-Variable-Magic [...] rt.cpan.org
From: Vincent Pit <perl [...] profvince.com>
On 31/01/2012 19:04, Peter Shangov via RT wrote: Show quoted text
> Queue: Variable-Magic > Ticket<URL: https://rt.cpan.org/Ticket/Display.html?id=74453> > > Thanks for the explanation. > > I needed this for MooseX::Params, a module that provides subroutine > signatures via attributes, and then makes the arguments available in a > magic read-only hash %_. I wanted to allow users to write this: > > sub doit :Args(ArrayRef foo) { > say for @{$_{foo}}; > } > > While still preventing > > sub doit :Args(ArrayRef foo) { > $_ = ... for @{$_{foo}}; > } > > I think I will just update the docs explaining why neither is allowed. > > Thanks again, > > --Peter >
If you want to prevent your users from modifying the values of your %_ hash, then this is a completely different matter : you just have to make it read-only. For example, you can put Readonly objects in %_, or recursively turn on the READONLY flag on its values (with the Internals::SvREADONLY core function). The 'store' magical callback is not suitable for this. Vincent.