Может ли подпрограмма Perl возвращать данные, но продолжать обработку?

Есть ли способ заставить подпрограмму отправлять данные обратно во время обработки? Например (этот пример используется просто для иллюстрации) - подпрограмма читает файл. Пока он читает файл, если выполняется какое-то условие, "верните" эту строку и продолжайте обработку. Я знаю, что есть те, кто ответит - зачем вам это делать? и почему бы тебе просто...?, но я действительно хотел бы знать, возможно ли это.

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;
}

Подпрограмма выполняет итерации по строкам, пока не найдет ту, которая соответствует шаблону. /foo/ а затем возвращает его, иначе ничего не возвращает. (undef в скалярном контексте.) Потому что файловый дескриптор $fh определяется вне области действия подпрограммы, он остается резидентным в памяти между вызовами. Самое главное, его состояние, включая текущую позицию поиска в файле, сохраняется. Таким образом, каждый вызов подпрограммы возобновляет чтение файла, в котором она была остановлена ​​в последний раз.

Чтобы использовать итератор:

while( defined( my $next_line = $iter->() ) ) { 
    # do something with each line here
}

Если вы действительно хотите сделать это, вы можете использовать потоки. Одним из вариантов может быть разветвление отдельного потока, который читает файл, и когда он находит определенную строку, поместите его в массив, который разделяется между потоками. Тогда другой поток может взять строки, как они найдены, и обработать их. Вот пример, который читает файл, ищет "X" в строке файла и выполняет действие, когда он найден.

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);
}

Попробуйте этот код как файл test.txt:

line 1
line 2X
line 3
line 4X
line 5
line 6
line 7X
line 8
line 9
line 10
line 11
line 12X

Если ваш язык поддерживает замыкания, вы можете сделать что-то вроде этого:

Кстати, функция не будет продолжать обрабатывать файл, она будет запускаться только тогда, когда вы ее вызываете, так что это может быть не то, что вам нужно.

(Это псевдокод, подобный 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();

Как насчет рекурсивного саба? зановоopenПри использовании существующих файловых дескрипторов номер строки ввода не сбрасывается, поэтому он продолжается с того места, где он остановлен.

Вот пример, где process_file подпрограмма распечатывает пустую строку, разделенную "\n\n" абзацы, которые содержат foo.

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);
Другие вопросы по тегам