Perl 5.10 gives us UNITCHECK. So now we have CHECK that works at program level, UNITCHECK that narrows it down to a unit (e.g. a module/file). The next step is to narrow it down further to a lexical scope. That's what this module does.
A problem with UNITCHECK blocks as of today is that there's no way for a module to add a UNITCHECK to the unit that uses the module, making the use quite limited. Until there's a solution to that (if there will be), Scope::CHECK is an IMHO good enough alternative. Most modules are used at file scope, and modules that need to use this module shouldn't be dynamically loaded.
Here's a minimal demonstration of lexical CHECK blocks that pin-points where the block is executed:
use 5.010;
use Scope::CHECK;
{
BEGIN { Scope::CHECK::->add(sub { say 'Checking scope' }) }
BEGIN { say 'End of scope' }
}
BEGIN { say 'After scope' }
__END__
End of scope
Checking scope
After scope
The complete .pm file is found below.
- What do you think of the name?
Scope:: already exists as a top-level name (Scope::Guard by chocolateboy), but there's also Hook::Scope by Artur Bergman. So guidelines from previous module are conflicting at best. If all goes well with this module I plan to also release Module::CHECK and Class::CHECK which are wrappers making it easier to use Scope::CHECK blocks for module and class authors. So I've adjusted the name according to those yet not released modules, as those fit the current CPAN scheme.
- What do you think of the interface?
I've considered making use Scope::CHECK sub { ... } equivalent with BEGIN { require Scope::CHECK; Scope::CHECK::->add(sub { ... }) } but I think it's a bit premature to do that now. The use statement may be useful for something else. Any ideas?
- What do you think of the implementation?
I've spotted two issues regarding threading (see the POD). Are there any other issues? Is it even safe to use %^H and DESTROY like that?
- What do you think of the documentation?
To sparse, too dense, too ... something? Anything missing?
- Anything else?
Update: It looks as though I've found a way to stop blocks to run prematurely due to threads. (See the BUGS section in the POD for the issue.)
Update: The verdict from p5p is that this is explicitly not supported.
package Scope::CHECK;
use 5.010;
$VERSION = 0.001;
use strict;
{
package Scope::CHECK::_OnScopeExit;
sub new { bless $_[1], $_[0] }
DESTROY { goto &{$_[0]} }
}
sub add {
state @queue;
state $Id = 0;
shift;
my ($code) = @_;
push @queue, $code;
$^H{__PACKAGE__ . '::_' . $Id++}
= Scope::CHECK::_OnScopeExit::->new(sub { (pop @queue)->() });
}
1;
__END__
=head1 NAME
Scope::CHECK - Add CHECK blocks to the currently compiling lexical sco
+pe
=head1 SYNOPSIS
use 5.010;
use strict;
use Scope::CHECK;
{
BEGIN { say '1. Compiling block.' }
BEGIN {
Scope::CHECK::->add(sub { say '4. Checking block, again.'
+});
}
BEGIN {
Scope::CHECK::->add(sub { say '3. Checking block.' });
}
BEGIN { say '2. Done compiling block.' }
}
BEGIN { say '5. Compiling rest of file.' }
say '6. Running file.';
__END__
1. Compiling block.
2. Done compiling block.
3. Checking block.
4. Checking block, again.
5. Compiling rest of file.
6. Running file.
=head1 DESCRIPTION
C<Scope::CHECK> lets you add C<CHECK> blocks to the currently compilin
+g lexical scope. You probably want to do this because you need to def
+er code until the whole scope is done compiling.
C<Scope::CHECK> blocks run in LIFO order, just as C<CHECK> and C<UNITC
+HECK> blocks.
C<Scope::CHECK> blocks at "unit scope", i.e. the same lexical scope as
+ C<UNITCHECK> lives in, currently execute before C<UNITCHECK> blocks.
+ This depends on the inner working of perl, and this order isn't defi
+ned anywhere as far as I know.
=head1 METHODS
=over
=item Scope::CHECK->add(CODE)
C<add> takes a subroutine reference as the only argument. The subrouti
+ne will be executed when the currently compiling lexical scope is com
+piled.
=back
=head1 BUGS
=over
=item Not really C<CHECK> blocks
As the blocks aren't really C<CHECK> blocks but only pretend to be, th
+ey might be executed too early before all compile passes are done. I
+don't know before/during/after which compile pass they're executed. I
+f you don't use C<Scope::CHECK> blocks to manipulate the optree you'r
+e probably safe. Let me know if you have any problems related to this
+.
=item Threads
C<Scope::CHECK> may not work in combination with threads. As long as y
+ou don't B<start> any threads while a C<Scope::CHECK> block is waitin
+g to execute or inside a C<Scope::CHECK> block you probably won't hav
+e any problem. Here's an example of what can happen when you start a
+thread before an active C<Scope::CHECK> block has been executed:
use 5.010;
use strict;
use Scope::CHECK;
use threads;
{
BEGIN { Scope::CHECK::->add(sub { say 'CHECK' }) }
BEGIN { threads::->create(sub { say 'Thread started and ended.
+' })->join }
BEGIN { say 'Block compiled.' }
}
BEGIN { say 'Continuing compiling.' }
__END__
Thread started and ended.
CHECK
Block compiled.
CHECK
Continuing compiling.
Notice the extra CHECK in the output. The first is because the thread
+ended, and the block was thus run prematurely.
If you find any way to detect that a block executed because a thread e
+nded, please let me know.
=item When no scope is compiling
If no scope is being compiled the block will be executed when the prog
+ram exits, after C<END> blocks. This is not by design but due to curr
+ent implementation.
=back
=head1 AUTHOR
lodin
=head1 COPYRIGHT
Copyright 2008 lodin. All rights reserved.
This library is free software; you can redistribute it and/or modify i
+t under the same terms as Perl itself.
=head1 SEE ALSO
L<perlmod> for C<CHECK> and C<UNITCHECK>.
=cut
lodin