Is there any way to have a subroutine send data back while still processing? For instance (this example used simply to illustrate) - a subroutine reads a file. While it is reading through the file, if some condition is met, then "return" that line and keep processing. I know there are those that will answer - why would you want to do that? and why don't you just ...?, but I really would like to know if this is possible.
7 回答
实现此类功能的常用方法是使用回调函数:
{
open my $log, '>', 'logfile' or die $!;
sub log_line {print $log @_}
}
sub process_file {
my ($filename, $callback) = @_;
open my $file, '<', $filename or die $!;
local $_;
while (<$file>) {
if (/some condition/) {
$callback->($_)
}
# whatever other processing you need ....
}
}
process_file 'myfile.txt', \&log_line;
或者甚至不命名回调:
process_file 'myfile.txt', sub {print STDERR @_};
一些语言使用“生成器”或“协程”提供这种特性,但 Perl 没有。上面链接的生成器页面有 Python、C# 和 Ruby(以及其他)的示例。
Coro模块看起来对这个问题很有用,虽然我不知道它是如何工作的,也不知道它是否做了它所宣传的。
在 Perl 中执行此操作的最简单方法可能是使用迭代器类型的解决方案。例如,这里我们有一个子程序,它在文件句柄上形成一个闭包:
open my $fh, '<', 'some_file.txt' or die $!;
my $iter = sub {
while( my $line = <$fh> ) {
return $line if $line =~ /foo/;
}
return;
}
sub 遍历这些行,直到找到与模式匹配的行/foo/
,然后返回它,否则不返回任何内容。(undef
在标量上下文中。)因为文件句柄$fh
是在子范围之外定义的,所以它在调用之间保持驻留在内存中。最重要的是,它的状态,包括文件中的当前查找位置,都被保留了下来。因此,对子例程的每次调用都会继续读取上次停止的文件。
要使用迭代器:
while( defined( my $next_line = $iter->() ) ) {
# do something with each line here
}
If you really want do this you can by using threading. One option would be to fork a separate thread that reads the file and when it finds a certain line, place it in an array that is shared between threads. Then the other thread could take the lines, as they are found, and process them. Here is an example that reads a file, looks for an 'X' in a file's line, and does an action when it is found.
use strict;
use threads;
use threads::shared;
my @ary : shared;
my $thr = threads->create('file_reader');
while(1){
my ($value);
{
lock(@ary);
if ($#ary > -1){
$value = shift(@ary);
print "Found a line to process: $value\n";
}
else{
print "no more lines to process...\n";
}
}
sleep(1);
#process $value
}
sub file_reader{
#File input
open(INPUT, "<test.txt");
while(<INPUT>){
my($line) = $_;
chomp($line);
print "reading $line\n";
if ($line =~ /X/){
print "pushing $line\n";
lock(@ary);
push @ary, $line;
}
sleep(4)
}
close(INPUT);
}
Try this code as the test.txt file:
line 1
line 2X
line 3
line 4X
line 5
line 6
line 7X
line 8
line 9
line 10
line 11
line 12X
那么递归子呢?修改open
现有文件句柄不会重置输入行号,因此它会从中断处继续。
这是一个示例,其中子例程打印出包含 fooprocess_file
的以空行分隔的段落。"\n\n"
sub process_file {
my ($fileHandle) = @_;
my $paragraph;
while ( defined(my $line = <$fileHandle>) and not eof(<$fileHandle>) ) {
$paragraph .= $line;
last unless length($line);
}
print $paragraph if $paragraph =~ /foo/;
goto &process_file unless eof($fileHandle);
# goto optimizes the tail recursion and prevents a stack overflow
# redo unless eof($fileHandle); would also work
}
open my $fileHandle, '<', 'file.txt';
process_file($fileHandle);
如果您的语言支持闭包,您可以执行以下操作:
顺便说一句,该函数不会继续处理文件,它只会在您调用它时运行,所以它可能不是您需要的。
(这是一个类似 javascript 的伪代码)
function fileReader (filename) {
var file = open(filename);
return function () {
while (s = file.read()) {
if (condition) {
return line;
}
}
return null;
}
}
a = fileReader("myfile");
line1 = a();
line2 = a();
line3 = a();