Category: |
Miscellaneous |
Author/Contact Info |
|
Description: |
This provides a way to produce static lexical variables in
Perl without relying on the bug mentioned at Unusual Closure Behaviour.
Ever since I pointed out this bug, I have not quite known
what to do about people who think that it is a feature.
Well I think it a good use of this, my 2000'th post, to
provide an alternate solution for people who want static
lexicals.
For an explanation of why the misfeature really is a bug,
see my $foo if 0; is a bug.
To understand how to use this module, read the
documentation. |
package Tie::Static;
$VERSION = 0.01;
foreach my $type (qw(Hash Array Scalar)) {
my $meth = uc($type);
eval qq(
package Tie::Static::$type;
require Tie::$type;
\@ISA = 'Tie::Std$type';
sub TIE$meth {
my \$class = shift;
my \$id = join "|", caller(), \@_;
return \$preserved{\$id}
||= \$class->SUPER::TIE$meth();
}
sub Tie::Static::TIE$meth {
shift;
unshift \@_, 'Tie::Static::$type';
goto &Tie::Static::$type\::TIE$meth;
}
) or die $@;
}
1;
__END__
=head1 NAME
Tie::Static - create static lexicals
=head1 SYNOPSIS
use Tie::Static;
sub foo {
tie (my $static_scalar, 'Tie::Static');
tie (my @static_array, 'Tie::Static');
tie (my %static_hash, 'Tie::Static');
# do whatever you want
}
=head1 DESCRIPTION
This module makes it easy to produce static variables.
A static variable is a variable whose value will remain
constant from invocation to invocation. The usual way
to produce this is to create an enclosing scope which
contains a lexically scoped variable. For instance the
example above could be written as:
{
my $static_scalar;
my @static_array;
my %static_hash;
sub foo {
# Do whatever you want
}
}
But while this works, many people find it cumbersome
to have to produce new scopes manually just to get a
static variables. This module provides an alternate
solution by providing a way to tie lexical variables
back to the same value each time.
As an additional feature, this module supports "modal
statics". If you pass additional arguments into the
tie, those arguments will be factored into the
decision of what static you get.
=head1 BUGS
This module uses the feedback from I<caller> to decide
what static to give you back. While this is good
enough for most possible uses of statics, it is not
always right. Aside from the possibility of someone
deliberately confusing I<caller>, closures will not,
in general, be distinguished from each other since
two instances will have the same package, filename,
and line number. You might argue that it is easy
enough to solve that by passing in a unique lexically
scoped variable as a mode. And it is. But in that
case the lexical that is your mode is already a static
and it would usually make more sense to create more
lexicals in that scope.
This only allows static scalars, arrays, and hashes.
If you want to overload the implementation of a static,
please note that scalars, arrays, and hashes are not
tied to the package Tie::Static. Instead they are tied
to the private packages Tie::Static::Scalar,
Tie::Static::Array, and Tie::Static::Hash.
=head1 AUTHOR AND COPYRIGHT
Ben Tilly (ben_tilly@operamail.com)
Copyright 2001. This may be modified and distributed
on the same terms as Perl.
|
Re: Tie::Static
by MeowChow (Vicar) on Jul 15, 2001 at 11:41 UTC
|
Any reason not to simplify this by losing the extra layer of indirection?
package Tie::Static;
$VERSION = 0.01;
foreach my $type (qw(Hash Array Scalar)) {
my $meth = uc($type);
eval qq(
package Tie::Static::$type;
require Tie::$type;
\@ISA = 'Tie::Std$type';
sub Tie::Static::TIE$meth {
my \$id = join "|", caller(), \@_;
return \$preserved{\$id}
||= Tie::Static::$type->TIE$meth;
}
) or die $@;
}
1;
You should also mention in the POD that it's not a good idea to do this:
tie (my $foo, 'Tie::Static'); tie (my $bar, 'Tie::Static');
# or this
map { tie $_, 'Tie::Static' } my ($foo, $bar, $baz, ...);
MeowChow
s aamecha.s a..a\u$&owag.print | [reply] [d/l] [select] |
|
The reason for the extra layer of indirection is that
when I wrote it I was thinking that you would tie to
the type of the variable. So you would tie to
Tie::Static::Hash, etc. In fact I am still unsure whether
it is better to always tie to Tie::Static, or to tie to
the actual package that you are blessed into. There is
something to be said for a consistent interface. There
is also something to be said for not violating expectations
about what tied is.
As for the examples you offer of broken ties, read the first
paragraph of "bugs". While it does not offer your examples,
it is clear about the fact that the heuristic uses
package, filename, and line number. Do you think it is
clear enough to warn people away from that map trick?
Personally I am inclined to believe that someone who wants
many tied variables will generally just tie a hash. Do
you think that is wrong? Would it make sense to offer a
function that takes a list of variables and ties them?
Something like this?
sub static {
my $called = join ":", caller();
tie ($_[$_], 'Tie::Static', $called, $_) for 0..$#_;
}
Then people can just call:
static my ($foo, $bar, $baz);
Yes? No? Maybe? | [reply] [d/l] [select] |
|
In fact I am still unsure whether it is better to always tie to Tie::Static, or to tie to the actual package that you are blessed into. There is something to be said for a consistent interface. There is also something to be said for not violating expectations about what tied is.
Well, it's not uncommon to see constructors bless their objects into other classes, so I don't think it's fair for a user to expect that tied return a reference blessed into the original class. Of course, we've seen a few people confused by the former, so it's safe to assume that some people will be confused by the latter.
Do you think it is clear enough to warn people away from that map trick?
I don't think so; it's a subtle bug that's easy for us to spot since we're familiar with the implementation, but for a casual user of the module, it wouldn't be obvious, even after reading through the bugs section. In general, I think one should enumerate the failure modes of a module as much as reasonably possible. I usually find that such documentation helps improve my understanding of a module's internals.
Then people can just call: static my ($foo, $bar, $baz); Yes? No? Maybe?
I really like this, though I'm a confessed syntactic sugar addict. It's much less verbose than the original interface, and nicely abstracts away the whole tied issue from view. The cost is another layer of indirection, though if you're using this module, performance is probably not high on your list of priorities. I also think that static should apply to lists and hashes:
sub static {
my $called = join ":", caller();
my $uniq;
for (@_) {
if (!ref) { tie $_, 'Tie::Static', $called, $uniq++
+ }
elsif (ref eq 'SCALAR') { tie $$_, 'Tie::Static', $called, $uniq++
+ }
elsif (ref eq 'ARRAY') { tie @$_, 'Tie::Static', $called, $uniq++
+ }
elsif (ref eq 'HASH') { tie %$_, 'Tie::Static', $called, $uniq++
+ }
}
}
# usage
static my $foo;
static \ my ($foo, @bar, %baz);
It has also occured to me that there should be a way to properly initialize a static variable. Although one could write:
static my $foo;
$foo = 'blah' unless defined $foo;
The second line could re-initialize $foo after it has been intentionally undef'd. I have some ideas about how to do this, but none of them are very pretty. Any thoughts?
MeowChow
s aamecha.s a..a\u$&owag.print | [reply] [d/l] [select] |
Tie::Static take 2
by tilly (Archbishop) on Jul 16, 2001 at 07:51 UTC
|
Here is another shot at Tie::Static that takes into account
considerable conversation with MeowChow, japhy and
others. If nobody notices anything glaring wrong with it,
it will go in my home directory as version 0.02.
package Tie::Static;
use Exporter;
@EXPORT_OK = 'static';
@ISA = 'Exporter';
$VERSION = 0.02;
use strict;
use vars qw(%call_count);
use Carp;
sub static {
my $call = join "|", caller();
if ($call_count{$call}) {
tie_all($call, @_);
}
else {
my @init = map {
(ref($_) eq "SCALAR" or ref($_) eq "REF") ? $$_
: (ref($_) eq "ARRAY") ? [@$_]
: (ref($_) eq "HASH") ? { %$_ }
: bad_ref($_);
} @_;
tie_all($call, @_);
foreach my $to_replace(@_) {
my $saved = shift @init;
if (ref($to_replace) eq "SCALAR" or ref($to_replace) eq "REF") {
$$to_replace = $saved;
}
elsif (ref($to_replace) eq "ARRAY") {
@$to_replace = @$saved;
}
elsif (ref($to_replace) eq "HASH") {
%$to_replace = %$saved;
}
else {
$Carp::Verbose = 1;
bad_ref($to_replace);
}
}
}
return $call_count{$call}++;
}
# The first argument is the value of $called to use, the
# rest are references to the variables to tie. It ties
# the variables to the appropriate static.
sub tie_all {
my $call = shift;
my $uniq = 0;
for (@_) {
if (ref($_) eq "SCALAR" or ref($_) eq "REF") {
tie ($$_, 'Tie::Static::Scalar', $call, $uniq++);
}
elsif (ref($_) eq "ARRAY") {
tie (@$_, 'Tie::Static::Array', $call, $uniq++);
}
elsif (ref($_) eq "HASH") {
tie (%$_, 'Tie::Static::Hash', $call, $uniq++);
}
else {
bad_ref($_);
}
}
}
# Message for a bad reference in the argument.
sub bad_ref {
my $thing = shift;
if (my $ref = ref($thing)) {
croak("Cannot create static of unknown type $ref");
}
else {
croak("Arguments to static must be references!");
}
}
# Implement the ties
foreach my $type (qw(Hash Array Scalar)) {
my $meth = uc($type);
my $pack = "Tie::Static::$type";
eval qq(
package $pack;
require Tie::$type;
\@$pack\::ISA = 'Tie::Std$type';
sub TIE$meth {
my \$class = shift;
my \$call = join "|", \@_ ? \@_ : caller();
return \$$pack\::preserved{\$call}
||= \$class->SUPER::TIE$meth();
}
sub Tie::Static::TIE$meth {
shift;
unshift \@_, 'Tie::Static::$type';
goto &$pack\::TIE$meth;
}
) or die $@;
}
1;
__END__
=head1 NAME
Tie::Static - create static lexicals
=head1 SYNOPSIS
# The tie-based approach
use Tie::Static;
sub foo {
tie (my $static_scalar, 'Tie::Static');
tie (my @static_array, 'Tie::Static');
tie (my %static_hash, 'Tie::Static');
# do whatever you want
}
# The function call approach
use Tie::Static qw(static);
sub bar {
static \ my ($scalar, @array, %hash);
# etc
}
=head1 DESCRIPTION
This module makes it easy to produce static variables.
A static variable is a variable whose value will remain
constant from invocation to invocation. The usual way
to produce this is to create an enclosing scope which
contains a lexically scoped variable. For instance the
first example could be written as:
{
my $static_scalar;
my @static_array;
my %static_hash;
sub foo {
# Do whatever you want
}
}
But while this works, many people find it cumbersome
to have to produce new scopes manually just to get a
static variables. This module provides an alternate
solution by providing a way to make lexical variables
be what they used to be.
There are two interfaces. The low-level interface is
to I<tie> your variable directly. But most of the time
you will want to use the exportable I<static> function.
If you I<tie> and do not pass any arguments, it will use
the feedback from caller() to decide whether to tie
you to a fresh variable, or whether to hand you back
an old one. If you pass the I<tie> arguments, it will
join them with "|" and use that key to decide what
object to hand you back. This allows you to create
static variables which are shared between functions in
any way you want.
What I<static> does is take a list of references to
variables, tie them, and then report how many
times they were previously tied. If the variables had
not been tied before, I<static> will initialize the tied
variables to the values they had before being tied.
Therefore if you want to have default values for your
static variables you can either initialize them before
calling I<static>, or do the initialization if I<static>
returns a false value. Here are examples:
# Pre-initializing a static.
my @array = 1..10;
my %hash = (Hello => "World", Greetings => "Earthlings");
static \(@array, %hash);
# Testing the return of static
my $handle;
unless (static(\$handle)) {
$handle = complex_initialization();
}
# Initializing while calling, only works with scalars
static \(my $foo = "Hello", my $bar = "World");
=head1 LIMITATIONS AND NOTES
This module relies on the output of [caller] to decide
which value to give back. Specifically, it makes its
decisions based on Perl's idea of the current package,
filename, and line-number. Normally this is correct.
But sometimes it is wrong. And occasionally it is
very wrong. It is correct if there is only one call
on any given line, and you want that call to always
give you back the same values. It is wrong if you
put 2 separate calls to I<static> or try to I<tie>
the same data-type twice on one line. It is very wrong
if you want to play with closures. It has no way to
distinguish them.
This only allows static scalars, arrays, and hashes.
If you want to overload the implementation of a static,
please note that scalars, arrays, and hashes are not
tied to the package Tie::Static. Instead they are tied
to the private packages Tie::Static::Scalar,
Tie::Static::Array, and Tie::Static::Hash.
=head1 CREDITS
Thanks go to several people at http://www.perlmonks.org
for discussions on how to implement this and what the
API should look like. In particular "MeowChow" for
analyzing the gotchas that people need to be aware of.
Jeff "japhy" Pinyan (japhy@pobox.com) for discussion on
implementations and the idea of I<static>. And
"HyperZonk" and Charles "Wog" Reiss for general
discussion. The idea of initializing scalars as you
call I<static> is Wog's.
And a particular note should be made of all of the
people on p5p, PerlMonks, and elsewhere who saw the
behaviour of
my $foo if 0;
as a feature rather than a bug. Without you I would not
have been inspired to write an (intentional)
implementation of statics for Perl.
=head1 AUTHOR AND COPYRIGHT
Ben Tilly (ben_tilly@operamail.com)
Copyright 2001. This may be modified and distributed
on the same terms as Perl.
| [reply] [d/l] |
|
(static my $x) = 'yz';
work. You have to get the parens right. But at least it's useful.
update Nevermind. you'd have to back into the unless defined $x bag all over again. (static my $x ) ||=  ?
  p
| [reply] [d/l] [select] |
|
Actually I had simpler reasons for avoiding that.
The first is that I didn't want to depend on experimental
5.6 features without good reason.
The second, and more important, is that I tried to figure
out what it would look like to declare and initialize a
list of things that included arrays and hashes. It did
not look pretty.
Anyways I think that the API that I came up with is both
simple to explain and pretty flexible. Plus it works on
5.005. :-)
| [reply] |
|
|