every($key, $interval) function

T

Ted Zlatanov

I find this function very useful:

{
my %counters = ();
sub every
{
my $id = shift @_;
my $when = shift @_;

return (0 == ++$counters{$id} % $when);
}
}

I call it like this:

print "debug info: ..."
if every("debug spot", 50);

print "extra info: ..."
if every("extra spot", 100);

So the debug info will be printed on every 50th call to every(). It's
small and convenient, but I have to make up a new ID every time.

I'd like to preserve the functional interface, so no OOP cheating. How
can I avoid the $id parameter, creating an automatically localized
every() call each time? I thought of __FILE__ and __LINE__ but those
are not unique enough, since you could potentially have

if (every(50)||every(33)) {}

on a line... I also thought of every(\&action_sub, 50) but that avoids
the problem sideways (it doesn't create a unique key, but makes the
action the key, which is not the same thing).

Ted
 
D

Dr.Ruud

Ted Zlatanov schreef:
{
my %counters = ();
sub every
{
my $id = shift @_;
my $when = shift @_;

return (0 == ++$counters{$id} % $when);
}
}

I call it like this:

print "debug info: ..."
if every("debug spot", 50);

print "extra info: ..."
if every("extra spot", 100);

So the debug info will be printed on every 50th call to every(). It's
small and convenient, but I have to make up a new ID every time.

I'd like to preserve the functional interface, so no OOP cheating.
How can I avoid the $id parameter, creating an automatically localized
every() call each time? I thought of __FILE__ and __LINE__ but those
are not unique enough, since you could potentially have

if (every(50)||every(33)) {}

on a line... I also thought of every(\&action_sub, 50) but that
avoids the problem sideways (it doesn't create a unique key, but
makes the action the key, which is not the same thing).

#!/usr/bin/perl
use strict;
use warnings;

sub every_factory {
my $d = shift;
my $n;
return sub { !(++$n % $d) }
}

my ($everyX, $everyY) = (every_factory(3), every_factory(5));

for my $i (1..50) {
print "$i:X\n" if $everyX->();
print "$i:Y\n" if $everyY->();
}
 
T

Ted Zlatanov

R> #!/usr/bin/perl
R> use strict;
R> use warnings;

R> sub every_factory {
R> my $d = shift;
R> my $n;
R> return sub { !(++$n % $d) }
R> }

R> my ($everyX, $everyY) = (every_factory(3), every_factory(5));

R> for my $i (1..50) {
R> print "$i:X\n" if $everyX->();
R> print "$i:Y\n" if $everyY->();
R> }

That's neat, creating $n and returning a subroutine that has it in
scope. I don't like it as much as every(50) because it separates the
number of cycles from the usage of the function, so you'd have to look
back up to remember how many cycles $everyX will use.

I was hoping to create something truly unique and use it as the key
automatically, like __FILE__:__LINE__ but allowing more than one per
line. I tried `caller 0' in list context but it is not enough.
__COLUMN__ would be perfect, if it existed (I don't know of such a
thing).

Ted
 
D

Dr.Ruud

Ted Zlatanov schreef:
Dr.Ruud:




That's neat, creating $n and returning a subroutine that has it in
scope.
"closure"

I don't like it as much as every(50) because it separates the
number of cycles from the usage of the function, so you'd have to look
back up to remember how many cycles $everyX will use.

Then just call it $every50.

Or compromise:

#!/usr/bin/perl
use strict;
use warnings;

sub every{
my %counters if 0;
my ($div, @id) = @_;
return !(++$counters{ caller(), $div, @id } % $div);
}

for my $i (1..13) {
print "A:$i\n" if every(3); print "B:$i\n" if every(3,"B");
print "C:$i\n" if every(5);
}
 
B

Ben Morrow

Quoth Ted Zlatanov said:
I find this function very useful:

{
my %counters = ();
sub every
{
my $id = shift @_;
my $when = shift @_;

return (0 == ++$counters{$id} % $when);
}
}

I call it like this:

print "debug info: ..."
if every("debug spot", 50);

print "extra info: ..."
if every("extra spot", 100);

So the debug info will be printed on every 50th call to every(). It's
small and convenient, but I have to make up a new ID every time.

I'd like to preserve the functional interface, so no OOP cheating. How
can I avoid the $id parameter, creating an automatically localized
every() call each time? I thought of __FILE__ and __LINE__ but those
are not unique enough, since you could potentially have

if (every(50)||every(33)) {}

on a line... I also thought of every(\&action_sub, 50) but that avoids
the problem sideways (it doesn't create a unique key, but makes the
action the key, which is not the same thing).

The obvious answer is to call it like

every(my $dummy, 50)

and key your hash on refaddr \$_[0], or, better, make it a fieldhash
(Hash::Util::Fieldhash, new with 5.10). An alternative would be to write
a tiny bit of XS that grabs the return op number from the top entry in
the call stack: basically, this would key on the place in the source
where every was called.

Ben
 
D

Dr.Ruud

Ben Morrow schreef:
Ted Zlatanov:
I find this function very useful:

{
my %counters = ();
sub every
{
my $id = shift @_;
my $when = shift @_;

return (0 == ++$counters{$id} % $when);
}
}

I call it like this:

print "debug info: ..."
if every("debug spot", 50);

print "extra info: ..."
if every("extra spot", 100);

So the debug info will be printed on every 50th call to every().
It's small and convenient, but I have to make up a new ID every time.

I'd like to preserve the functional interface, so no OOP cheating.
How can I avoid the $id parameter, creating an automatically
localized every() call each time? I thought of __FILE__ and
__LINE__ but those are not unique enough, since you could
potentially have

if (every(50)||every(33)) {}

on a line... I also thought of every(\&action_sub, 50) but that
avoids the problem sideways (it doesn't create a unique key, but
makes the action the key, which is not the same thing).

The obvious answer is to call it like
every(my $dummy, 50)
and key your hash on refaddr \$_[0]
[...]

#!/usr/bin/perl
use strict;
use warnings;

sub every{
my %counters if 0;
(++$counters{ \$_[1] } % $_[0]) or $_[1] and print $_[1], "\n";
}

for my $i (1..13) {
every(7, my $ev0);
every(3, my $ev1 = "A:$i");
every(3, my $ev2 = "B:$i");
every(5, my $ev3 = "C:$i");
}
 
U

Uri Guttman

R> sub every{
R> my %counters if 0;

don't use that broken idiom. it will fail under 5.10 since it supports
state variables.

R> my ($div, @id) = @_;
R> return !(++$counters{ caller(), $div, @id } % $div);

just for education of others, that is the old pseudo multilevel hash
from perl4 days. it is rare to see a good use for it these days.

uri
 
D

Dr.Ruud

Uri Guttman schreef:
Ruud:

don't use that broken idiom.

Here it hasn't broken yet.

it will fail under 5.10 since it supports
state variables.

I'll try not to forget to add a comment like "statical, of course use a
state variable in Perl >=5.10" next time.
Or maybe better, refer to `perldoc -q static`.

just for education of others, that is the old pseudo multilevel hash
from perl4 days. it is rare to see a good use for it these days.

See also "$;" in `perldoc perlvar`.
 
T

Ted Zlatanov

R> Then just call it $every50.

The point is I'm forced to think of a variable and how to use it, plus I
need to initialize it, instead of saying every(50). I think that
complicates life for the user because the code is not simple enough
(it's neat, as I already mentioned, but just not simple).

R> Or compromise:

R> #!/usr/bin/perl
R> use strict;
R> use warnings;

R> sub every{
R> my %counters if 0;
R> my ($div, @id) = @_;
R> return !(++$counters{ caller(), $div, @id } % $div);
R> }

R> for my $i (1..13) {
R> print "A:$i\n" if every(3); print "B:$i\n" if every(3,"B");
R> print "C:$i\n" if every(5);
R> }

R> Ben Morrow schreef:
The obvious answer is to call it like
every(my $dummy, 50)
and key your hash on refaddr \$_[0]
[...]

R> #!/usr/bin/perl
R> use strict;
R> use warnings;

R> sub every{
R> my %counters if 0;
R> (++$counters{ \$_[1] } % $_[0]) or $_[1] and print $_[1], "\n";
R> }

R> for my $i (1..13) {
R> every(7, my $ev0);
R> every(3, my $ev1 = "A:$i");
R> every(3, my $ev2 = "B:$i");
R> every(5, my $ev3 = "C:$i");
R> }

Both of those are close but still requires thinking because the code is
not smart enough (as I mentioned, a __COLUMN__ value similar to __LINE__
and __FILE__ would do the right thing, or see below for opcode fun).
The goal is to do

warn "five or six" if (every(5) || every(6));

without external modules, if possible. I don't want to think about the
every() function whenever I use it: the bugs that would come from the
example above if it was badly implemented are just too nasty.

So far it seems like Ben's idea is the best way to do what I mean with
core Perl, but it still bothers me that I can't avoid creating state
*outside* the function call despite what caller() offers. Please
understand, I like all the solutions and they are clever, but they don't
get to the simplicity of the example above.


BM> An alternative would be to write a tiny bit of XS that grabs the
BM> return op number from the top entry in the call stack: basically,
BM> this would key on the place in the source where every was called.

That's exactly what I'd like, without the XS :)

Could I do it with Inline::C? How about a module that extends caller()
to do this (the DB override to caller() doesn't seem sufficient)? Maybe
one of the core debugging/profiling modules already provides this
information? I couldn't find it.

Thanks to everyone for the suggestions!
Ted
 
U

Uri Guttman

R> Uri Guttman schreef:
R> Here it hasn't broken yet.

it is broken now as it isn't documented. it is a bug in perl that lets
that code 'work'. using it is a poor idea and even worse showing it to
others on usenet. it is trivial to get such a thing with a block surrounding
the sub and putting the my variable out there. there is no reason to use
the my/if code and plenty of reasons to not use it.

uri
 
U

Uri Guttman

BaB> Uri Guttman wrote:
R> sub every{
R> my %counters if 0;
BaB> "our %counters" works.

and that declares and uses a global which can be modified by outside
code. not a better solution.

uri
 
U

Uri Guttman

BaB> Uri Guttman wrote:
BaB> "our %counters" works.
BaB> I'd thought that to - so I tested it by trying to access %counters
BaB> at the end of the script:

BaB> sub every{
BaB> # my %counters if 0;
BaB> our %counters;
BaB> (++$counters{ \$_[1] } % $_[0]) or $_[1] and print $_[1], "\n";
BaB> }

BaB> for my $i (1..13) {
BaB> every(7, my $ev0);
BaB> every(3, my $ev1 = "A:$i");
BaB> every(3, my $ev2 = "B:$i");
BaB> every(5, my $ev3 = "C:$i");
BaB> }

BaB> print keys %counters, "\n";


BaB> [mysys]: /usr/bin/perl -wc x.pl
BaB> Variable "%counters" is not imported at x.pl line 18.
BaB> Global symbol "%counters" requires explicit package name at x.pl line 18.
BaB> x.pl had compilation errors.

try it with %main::counters.

you don't understand how our works. it declares a lexically scoped name
(really an alias) to a global in the current namespace. so with strict
you need to fully qualify the name to access it. but it is still a
global and accessible from anywhere.

BaB> So it actually seems to work (removing line 18 lets the script run,
BaB> and it produces the same results as the "my %counters" version).

no, as i said it still is a global and doesn't hide the counter hash at
all.

uri
 
D

Dr.Ruud

Ted Zlatanov schreef:
The goal is to do

warn "five or six" if (every(5) || every(6));


Use (any variant of) my second approach:

#!/usr/bin/perl
use strict;
use warnings;

sub every{
my %counters if 0;
my ($div, @id) = @_;
return !(++$counters{ caller(), $div, @id } % $div);
}

for my $i (1..32) {
print "five or six:\t$i\n" if every(5) | every(6);
}
__END__

five or six: 5
five or six: 6
five or six: 10
five or six: 12
five or six: 15
five or six: 18
five or six: 20
five or six: 24
five or six: 25
five or six: 30


You just can't use the shortcutting "||", because the rightside every()
would not be called when the leftside every() already is true. (So you
had an xy-problem.)
 
T

Ted Zlatanov

R> Use (any variant of) my second approach:

R> #!/usr/bin/perl
R> use strict;
R> use warnings;

R> sub every{
R> my %counters if 0;
R> my ($div, @id) = @_;
R> return !(++$counters{ caller(), $div, @id } % $div);
R> }

R> for my $i (1..32) {
R> print "five or six:\t$i\n" if every(5) | every(6);
R> }
R> __END__

R> You just can't use the shortcutting "||", because the rightside every()
R> would not be called when the leftside every() already is true. (So you
R> had an xy-problem.)

OK, this will work for my purposes just fine, but it has one possible
bug (unlikely, granted, but it could happen in a one-liner):

for my $i (1..32) {
print "four once:\t$i\n" if every(4); print "four twice:\t$i\n" if every(4);
}

four twice: 2
four twice: 4
four twice: 6
four twice: 8
four twice: 10
four twice: 12
four twice: 14
four twice: 16
four twice: 18
four twice: 20
four twice: 22
four twice: 24
four twice: 26
four twice: 28
four twice: 30
four twice: 32

As a challenge, can anyone think of a way to fix that without using
extra parameters in @id, keeping the calling semantics just as they are
above, using only core modules (as of 5.10)? I couldn't find a way. My
original problem is solved, this is just for fun.

Ted
 
D

Dr.Ruud

Ted Zlatanov schreef:
it has one possible
bug (unlikely, granted, but it could happen in a one-liner):

for my $i (1..32) {
print "four once:\t$i\n" if every(4); print "four twice:\t$i\n"
if every(4); }

Yes, that's exactly why I added @id. Did you try 4.1 and 4.2 already? :)
 
D

Dr.Ruud

Ted Zlatanov schreef:
As a challenge, can anyone think of a way to fix that without using
extra parameters in @id, keeping the calling semantics just as they
are above

With a change in calling semantics:

#!/usr/bin/perl
use strict;
use warnings;

sub every{ my %n if 0; !(++$n{ caller(), $_[0] } % ${$_[0]}) }

for my $i (1..32) {
print "four:\t$i\n" if every(\4); print "four again:\t$i\n" if
every(\4);
}
 
T

Ted Zlatanov

R> With a change in calling semantics:

R> #!/usr/bin/perl
R> use strict;
R> use warnings;

R> sub every{ my %n if 0; !(++$n{ caller(), $_[0] } % ${$_[0]}) }

R> for my $i (1..32) {
R> print "four:\t$i\n" if every(\4); print "four again:\t$i\n" if
R> every(\4);
R> }

That's *nice*. I didn't know Perl would optimize \4 to be a unique
reference per execution, yet distinguish between two of them named
separately:

for my $i (1..32)
{
# prints two distinct SCALAR refs that are the same each time
print \4, \4,"\n";
}

That's wonderfully consistent.

R> Did you try 4.1 and 4.2 already? :)

That's also a neat way around it, but I may as well use extra calling
arguments if I'm going to do that.

Thanks for the great ideas.
Ted
 
T

Ted Zlatanov

Just a followup: every() has been very useful for progress counters,
e.g.:

sub print_update
{
print '.' if every(50);
print "\n" if every(500);
}

Plus I've used it for other things, like doing a DB commit every N
items, etc.

Here's the final version I'm using, from Dr. Ruud's suggestions:

{
my %counters;
sub every
{
my ($div, @id) = @_;
return !(++$counters{ caller(), $div, @id } % $div);
}
}

Ted
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,049
Latest member
Allen00Reed

Latest Threads

Top